///|
/// **moobile 的类型化样式层。**
///
/// 设计要点(对应 README「架构级改造的空间」):
///
/// 1. **属性集是封闭的** —— 只有下面这些方法存在。`display:grid`、
/// `position:sticky`、`::before` 这些**根本没有构造器**,
/// 所以"Web 能跑、移动端不能"在**编译期就不可能发生**。
/// (DESIGN 原则 3:可移植子集优先于表达力。)
///
/// 2. **每个属性的值类型由签名钉死** —— `font_weight` 只收 `FontWeight`,
/// 所以不可能出现"把数字塞给 fontWeight"这种静默非法。
///
/// 3. **一份数据、两个后端** —— `StyleValue` 同时携带"是不是长度",
/// 于是 RN 侧产出数字、CSS 侧产出 `"16px"`,不需要两边各存一套。
///
/// 词表取自 **React Native 的 style props**(它本身就已经是一份被策展过的
/// 可移植子集:flexbox + 有限属性集,且刻意排除了 grid / sticky / 伪元素)。
pub struct Style {
entries : Array[(String, StyleValue)]
}
///|
/// 值的形态,已足够覆盖 RN 与 CSS 的 style 取值:
///
/// | 构造器 | RN 产出 | CSS 产出 |
/// |---|---|---|
/// | `Unitless(d)` | 数字 | `"1"` |
/// | `Px(d)` | 数字 | `"16px"` |
/// | `Pct(d)` | `"50%"` | `"50%"` |
/// | `Em(d)` | 数字(按同一样式里的 `fontSize` 换算) | `"0.1em"` |
/// | `Auto` | `"auto"` | `"auto"` |
/// | `Str(s)` | 字符串 | 字符串 |
///
/// ★ `Em` 是实测补上的:yi 大量使用 `letter-spacing: .1em` 这类相对单位,
/// 而 **RN 的 `letterSpacing` 只认点数**。为它单独设构造器,
/// 就把"每次调用点手工乘一遍"这个易错动作消灭了。
pub(all) enum StyleValue {
Unitless(Double)
Px(Double)
Pct(Double)
Em(Double)
Auto
Str(String)
} derive(Eq)
///|
pub fn Style::new() -> Style {
{ entries: [] }
}
///|
/// 取出条目(后端按顺序消费)。
pub fn Style::entries(self : Style) -> Array[(String, StyleValue)] {
self.entries
}
///|
pub fn Style::is_empty(self : Style) -> Bool {
self.entries.length() == 0
}
// ---------------------------------------------------------------- 内部写入器
///|
fn Style::unitless(self : Style, key : String, v : Double) -> Style {
self.entries.push((key, Unitless(v)))
self
}
///|
fn Style::len(self : Style, key : String, v : Double) -> Style {
self.entries.push((key, Px(v)))
self
}
///|
fn Style::kw(self : Style, key : String, v : String) -> Style {
self.entries.push((key, Str(v)))
self
}
///|
fn Style::dim(self : Style, key : String, v : Dimension) -> Style {
self.entries.push((key, v.to_style_value()))
self
}
// ------------------------------------------------------------------ 关键字枚举
// 刻意只列 Web 与 RN 都有等价物的取值。
///|
pub(all) enum FlexDirection {
Row
Column
RowReverse
ColumnReverse
}
///|
pub fn FlexDirection::to_string(self : FlexDirection) -> String {
match self {
Row => "row"
Column => "column"
RowReverse => "row-reverse"
ColumnReverse => "column-reverse"
}
}
///|
pub(all) enum FlexWrap {
NoWrap
Wrap
WrapReverse
}
///|
pub fn FlexWrap::to_string(self : FlexWrap) -> String {
match self {
NoWrap => "nowrap"
Wrap => "wrap"
WrapReverse => "wrap-reverse"
}
}
///|
pub(all) enum Align {
FlexStart
FlexEnd
Center
Stretch
Baseline
}
///|
pub fn Align::to_string(self : Align) -> String {
match self {
FlexStart => "flex-start"
FlexEnd => "flex-end"
Center => "center"
Stretch => "stretch"
Baseline => "baseline"
}
}
///|
pub(all) enum AlignSelf {
Auto
FlexStart
FlexEnd
Center
Stretch
Baseline
}
///|
pub fn AlignSelf::to_string(self : AlignSelf) -> String {
match self {
Auto => "auto"
FlexStart => "flex-start"
FlexEnd => "flex-end"
Center => "center"
Stretch => "stretch"
Baseline => "baseline"
}
}
///|
pub(all) enum Justify {
FlexStart
FlexEnd
Center
SpaceBetween
SpaceAround
SpaceEvenly
}
///|
pub fn Justify::to_string(self : Justify) -> String {
match self {
FlexStart => "flex-start"
FlexEnd => "flex-end"
Center => "center"
SpaceBetween => "space-between"
SpaceAround => "space-around"
SpaceEvenly => "space-evenly"
}
}
///|
pub(all) enum Position {
Relative
Absolute
}
///|
pub fn Position::to_string(self : Position) -> String {
match self {
Relative => "relative"
Absolute => "absolute"
}
}
///|
pub(all) enum Overflow {
Visible
Hidden
Scroll
}
///|
pub fn Overflow::to_string(self : Overflow) -> String {
match self {
Visible => "visible"
Hidden => "hidden"
Scroll => "scroll"
}
}
///|
pub(all) enum BorderStyle {
Solid
Dotted
Dashed
}
///|
pub fn BorderStyle::to_string(self : BorderStyle) -> String {
match self {
Solid => "solid"
Dotted => "dotted"
Dashed => "dashed"
}
}
///|
/// ⚠️ 注意:`fontWeight` 在 RN 里是**字符串**,不是数字。
/// 把它做成枚举,就杜绝了"被 `Number()` 吃掉变成 700"这个静默 bug(README 的 F3)。
pub(all) enum FontWeight {
Normal
Bold
W100
W200
W300
W400
W500
W600
W700
W800
W900
}
///|
pub fn FontWeight::to_string(self : FontWeight) -> String {
match self {
Normal => "normal"
Bold => "bold"
W100 => "100"
W200 => "200"
W300 => "300"
W400 => "400"
W500 => "500"
W600 => "600"
W700 => "700"
W800 => "800"
W900 => "900"
}
}
///|
pub(all) enum FontStyle {
Normal
Italic
}
///|
pub fn FontStyle::to_string(self : FontStyle) -> String {
match self {
Normal => "normal"
Italic => "italic"
}
}
///|
pub(all) enum TextAlign {
Auto
Left
Right
Center
Justify
}
///|
pub fn TextAlign::to_string(self : TextAlign) -> String {
match self {
Auto => "auto"
Left => "left"
Right => "right"
Center => "center"
Justify => "justify"
}
}
///|
pub(all) enum TextDecorationLine {
None
Underline
LineThrough
UnderlineLineThrough
}
///|
pub fn TextDecorationLine::to_string(self : TextDecorationLine) -> String {
match self {
None => "none"
Underline => "underline"
LineThrough => "line-through"
UnderlineLineThrough => "underline line-through"
}
}
///|
pub(all) enum TextTransform {
None
Uppercase
Lowercase
Capitalize
}
///|
pub fn TextTransform::to_string(self : TextTransform) -> String {
match self {
None => "none"
Uppercase => "uppercase"
Lowercase => "lowercase"
Capitalize => "capitalize"
}
}
///|
/// 尺寸:`auto` / 绝对长度 / 百分比。用类型把它们分开,就不需要
/// `width` / `width_pct` / `width_auto` 三个方法。
pub(all) enum Dimension {
Auto
Px(Double)
Pct(Double)
}
///|
pub fn Dimension::to_style_value(self : Dimension) -> StyleValue {
match self {
Auto => Auto
Px(d) => Px(d)
Pct(d) => Pct(d)
}
}
// ------------------------------------------------------------------ 简短构造器
// 让调用点写 `@style.px(16)` 而不是 `@style.Dimension::Px(16)`。
///|
pub fn px(d : Double) -> Dimension {
Px(d)
}
///|
pub fn pct(d : Double) -> Dimension {
Pct(d)
}
///|
pub fn auto() -> Dimension {
Auto
}
// ---------------------------------------------------------------------- 布局
///|
pub fn Style::flex(self : Style, v : Double) -> Style {
self.unitless("flex", v)
}
///|
pub fn Style::flex_direction(self : Style, v : FlexDirection) -> Style {
self.kw("flexDirection", v.to_string())
}
///|
pub fn Style::flex_wrap(self : Style, v : FlexWrap) -> Style {
self.kw("flexWrap", v.to_string())
}
///|
pub fn Style::flex_grow(self : Style, v : Double) -> Style {
self.unitless("flexGrow", v)
}
///|
pub fn Style::flex_shrink(self : Style, v : Double) -> Style {
self.unitless("flexShrink", v)
}
///|
pub fn Style::flex_basis(self : Style, v : Dimension) -> Style {
self.dim("flexBasis", v)
}
///|
pub fn Style::align_items(self : Style, v : Align) -> Style {
self.kw("alignItems", v.to_string())
}
///|
pub fn Style::align_self(self : Style, v : AlignSelf) -> Style {
self.kw("alignSelf", v.to_string())
}
///|
pub fn Style::justify_content(self : Style, v : Justify) -> Style {
self.kw("justifyContent", v.to_string())
}
///|
pub fn Style::position(self : Style, v : Position) -> Style {
self.kw("position", v.to_string())
}
///|
pub fn Style::top(self : Style, v : Dimension) -> Style {
self.dim("top", v)
}
///|
pub fn Style::right_(self : Style, v : Dimension) -> Style {
self.dim("right", v)
}
///|
pub fn Style::bottom(self : Style, v : Dimension) -> Style {
self.dim("bottom", v)
}
///|
pub fn Style::left(self : Style, v : Dimension) -> Style {
self.dim("left", v)
}
///|
pub fn Style::z_index(self : Style, v : Double) -> Style {
self.unitless("zIndex", v)
}
///|
pub fn Style::overflow(self : Style, v : Overflow) -> Style {
self.kw("overflow", v.to_string())
}
// ---------------------------------------------------------------------- 尺寸
///|
pub fn Style::width(self : Style, v : Dimension) -> Style {
self.dim("width", v)
}
///|
pub fn Style::height(self : Style, v : Dimension) -> Style {
self.dim("height", v)
}
///|
pub fn Style::min_width(self : Style, v : Dimension) -> Style {
self.dim("minWidth", v)
}
///|
pub fn Style::max_width(self : Style, v : Dimension) -> Style {
self.dim("maxWidth", v)
}
///|
pub fn Style::min_height(self : Style, v : Dimension) -> Style {
self.dim("minHeight", v)
}
///|
pub fn Style::max_height(self : Style, v : Dimension) -> Style {
self.dim("maxHeight", v)
}
///|
pub fn Style::aspect_ratio(self : Style, v : Double) -> Style {
self.unitless("aspectRatio", v)
}
// ---------------------------------------------------------------------- 间隙
// RN 0.71+ 与 RNW 都支持 gap,属于可移植子集。
///|
pub fn Style::gap(self : Style, v : Double) -> Style {
self.len("gap", v)
}
///|
pub fn Style::row_gap(self : Style, v : Double) -> Style {
self.len("rowGap", v)
}
///|
pub fn Style::column_gap(self : Style, v : Double) -> Style {
self.len("columnGap", v)
}
// ---------------------------------------------------------------------- 外边距
///|
pub fn Style::margin(self : Style, v : Dimension) -> Style {
self.dim("margin", v)
}
///|
pub fn Style::margin_top(self : Style, v : Dimension) -> Style {
self.dim("marginTop", v)
}
///|
pub fn Style::margin_bottom(self : Style, v : Dimension) -> Style {
self.dim("marginBottom", v)
}
///|
pub fn Style::margin_left(self : Style, v : Dimension) -> Style {
self.dim("marginLeft", v)
}
///|
pub fn Style::margin_right(self : Style, v : Dimension) -> Style {
self.dim("marginRight", v)
}
///|
pub fn Style::margin_horizontal(self : Style, v : Dimension) -> Style {
self.dim("marginHorizontal", v)
}
///|
pub fn Style::margin_vertical(self : Style, v : Dimension) -> Style {
self.dim("marginVertical", v)
}
// ---------------------------------------------------------------------- 内边距
///|
pub fn Style::padding(self : Style, v : Dimension) -> Style {
self.dim("padding", v)
}
///|
pub fn Style::padding_top(self : Style, v : Dimension) -> Style {
self.dim("paddingTop", v)
}
///|
pub fn Style::padding_bottom(self : Style, v : Dimension) -> Style {
self.dim("paddingBottom", v)
}
///|
pub fn Style::padding_left(self : Style, v : Dimension) -> Style {
self.dim("paddingLeft", v)
}
///|
pub fn Style::padding_right(self : Style, v : Dimension) -> Style {
self.dim("paddingRight", v)
}
///|
pub fn Style::padding_horizontal(self : Style, v : Dimension) -> Style {
self.dim("paddingHorizontal", v)
}
///|
pub fn Style::padding_vertical(self : Style, v : Dimension) -> Style {
self.dim("paddingVertical", v)
}
// ---------------------------------------------------------------------- 边框
///|
pub fn Style::border_width(self : Style, v : Double) -> Style {
self.len("borderWidth", v)
}
///|
pub fn Style::border_top_width(self : Style, v : Double) -> Style {
self.len("borderTopWidth", v)
}
///|
pub fn Style::border_bottom_width(self : Style, v : Double) -> Style {
self.len("borderBottomWidth", v)
}
///|
pub fn Style::border_left_width(self : Style, v : Double) -> Style {
self.len("borderLeftWidth", v)
}
///|
pub fn Style::border_right_width(self : Style, v : Double) -> Style {
self.len("borderRightWidth", v)
}
///|
pub fn Style::border_color(self : Style, v : String) -> Style {
self.kw("borderColor", v)
}
///|
pub fn Style::border_style(self : Style, v : BorderStyle) -> Style {
self.kw("borderStyle", v.to_string())
}
///|
pub fn Style::border_radius(self : Style, v : Double) -> Style {
self.len("borderRadius", v)
}
///|
pub fn Style::border_top_left_radius(self : Style, v : Double) -> Style {
self.len("borderTopLeftRadius", v)
}
///|
pub fn Style::border_top_right_radius(self : Style, v : Double) -> Style {
self.len("borderTopRightRadius", v)
}
///|
pub fn Style::border_bottom_left_radius(self : Style, v : Double) -> Style {
self.len("borderBottomLeftRadius", v)
}
///|
pub fn Style::border_bottom_right_radius(self : Style, v : Double) -> Style {
self.len("borderBottomRightRadius", v)
}
// ---------------------------------------------------------------------- 视觉
///|
pub fn Style::background_color(self : Style, v : String) -> Style {
self.kw("backgroundColor", v)
}
///|
pub fn Style::opacity(self : Style, v : Double) -> Style {
self.unitless("opacity", v)
}
// ---------------------------------------------------------------------- 文字
///|
pub fn Style::color(self : Style, v : String) -> Style {
self.kw("color", v)
}
///|
pub fn Style::font_size(self : Style, v : Double) -> Style {
self.len("fontSize", v)
}
///|
pub fn Style::font_family(self : Style, v : String) -> Style {
self.kw("fontFamily", v)
}
///|
pub fn Style::font_weight(self : Style, v : FontWeight) -> Style {
self.kw("fontWeight", v.to_string())
}
///|
pub fn Style::font_style(self : Style, v : FontStyle) -> Style {
self.kw("fontStyle", v.to_string())
}
///|
pub fn Style::line_height(self : Style, v : Double) -> Style {
self.len("lineHeight", v)
}
///|
pub fn Style::letter_spacing(self : Style, v : Double) -> Style {
self.len("letterSpacing", v)
}
///|
pub fn Style::text_align(self : Style, v : TextAlign) -> Style {
self.kw("textAlign", v.to_string())
}
///|
pub fn Style::text_decoration_line(self : Style, v : TextDecorationLine) -> Style {
self.kw("textDecorationLine", v.to_string())
}
///|
pub fn Style::text_transform(self : Style, v : TextTransform) -> Style {
self.kw("textTransform", v.to_string())
}
// ---------------------------------------- 补齐:yi 的 CSS 用到的可移植属性
//
// 清单来自 `docs/evidence/r1/style_gap.md` 的差集分析(yi 的 198 条 CSS 共 57 个属性名,
// 本层原有 34 个覆盖)。下面这批是**可移植且 RN 支持**的,补齐它们;
// 不可移植的(grid / 伪元素 / transition / cursor …)在 README 里另有降级策略,
// 刻意不加进类型 —— 让它们在编译期就写不出来。
///|
pub(all) enum WhiteSpace {
Normal
NoWrap
Pre
PreWrap
}
///|
pub fn WhiteSpace::to_string(self : WhiteSpace) -> String {
match self {
Normal => "normal"
NoWrap => "nowrap"
Pre => "pre"
PreWrap => "pre-wrap"
}
}
///|
/// 只留 `flex` 与 `none`。**刻意不含 `grid`** —— 那正是要避免的东西。
pub(all) enum Display {
Flex
Hidden
}
///|
pub fn Display::to_string(self : Display) -> String {
match self {
Flex => "flex"
Hidden => "none"
}
}
///|
pub(all) enum TextDecorationStyle {
Solid
Double_
Dotted
Dashed
}
///|
pub fn TextDecorationStyle::to_string(self : TextDecorationStyle) -> String {
match self {
Solid => "solid"
Double_ => "double"
Dotted => "dotted"
Dashed => "dashed"
}
}
///|
pub fn Style::display(self : Style, v : Display) -> Style {
self.kw("display", v.to_string())
}
///|
pub fn Style::white_space(self : Style, v : WhiteSpace) -> Style {
self.kw("whiteSpace", v.to_string())
}
///|
/// `order` —— flex 排序。yi 用它把某块提到前面(`order: -1`)。
pub fn Style::order(self : Style, v : Double) -> Style {
self.unitless("order", v)
}
///|
pub fn Style::border_top_color(self : Style, v : String) -> Style {
self.kw("borderTopColor", v)
}
///|
pub fn Style::border_bottom_color(self : Style, v : String) -> Style {
self.kw("borderBottomColor", v)
}
///|
pub fn Style::border_left_color(self : Style, v : String) -> Style {
self.kw("borderLeftColor", v)
}
///|
pub fn Style::border_right_color(self : Style, v : String) -> Style {
self.kw("borderRightColor", v)
}
///|
pub fn Style::border_top_style(self : Style, v : BorderStyle) -> Style {
self.kw("borderTopStyle", v.to_string())
}
///|
pub fn Style::border_bottom_style(self : Style, v : BorderStyle) -> Style {
self.kw("borderBottomStyle", v.to_string())
}
///|
pub fn Style::border_left_style(self : Style, v : BorderStyle) -> Style {
self.kw("borderLeftStyle", v.to_string())
}
///|
pub fn Style::border_right_style(self : Style, v : BorderStyle) -> Style {
self.kw("borderRightStyle", v.to_string())
}
///|
pub fn Style::text_decoration_color(self : Style, v : String) -> Style {
self.kw("textDecorationColor", v)
}
///|
pub fn Style::text_decoration_style(self : Style, v : TextDecorationStyle) -> Style {
self.kw("textDecorationStyle", v.to_string())
}
///|
/// 相对字号的字距:`letter-spacing: .1em`。
/// RN 侧会按同一样式里的 `fontSize` 换算成点数(见 README 的 R1-3)。
pub fn Style::letter_spacing_em(self : Style, ratio : Double) -> Style {
self.entries.push(("letterSpacing", Em(ratio)))
self
}
///|
/// 相对字号的行动高。
pub fn Style::line_height_em(self : Style, ratio : Double) -> Style {
self.entries.push(("lineHeight", Em(ratio)))
self
}
// ------------------------------------------------------------------ 序列化
///|
/// RN 侧取值:长度与无单位量都是**数字**(RN 不接受 `"16px"`)。
///
/// `Em` 需要上下文里的字号,所以这里返回 `None`;
/// 由后端在知道同一样式的 `fontSize` 时另行换算(见 `Em::resolve`)。
pub fn StyleValue::to_rn_number(self : StyleValue) -> Double? {
match self {
Unitless(d) => Some(d)
Px(d) => Some(d)
_ => None
}
}
///|
/// RN 侧取值:非数字的一律是**字符串**。
pub fn StyleValue::to_rn_string(self : StyleValue) -> String? {
match self {
Pct(d) => Some("\{d}%")
Auto => Some("auto")
Str(s) => Some(s)
_ => None
}
}
///|
/// 把 `Em` 按给定字号换算成点数。RN 没有 `em` 这个概念,只能换算。
pub fn StyleValue::resolve_em(self : StyleValue, font_size : Double) -> StyleValue {
match self {
Em(ratio) => Px(ratio * font_size)
other => other
}
}
///|
/// 从一份样式里找字号,供 `Em` 换算。找不到时返回 `None`。
pub fn Style::font_size_of(self : Style) -> Double? {
for entry in self.entries {
if entry.0 == "fontSize" {
match entry.1 {
Px(d) | Unitless(d) => return Some(d)
_ => ()
}
}
}
None
}
///|
/// CSS 侧取值:长度要补 `px`,无单位量不补,`em` **原样保留**(CSS 认识它)。
pub fn StyleValue::to_css(self : StyleValue) -> String {
match self {
Unitless(d) => "\{d}"
Px(d) => "\{d}px"
Pct(d) => "\{d}%"
Em(d) => "\{d}em"
Auto => "auto"
Str(s) => s
}
}
///|
/// RN 的键是 camelCase(`backgroundColor`),CSSOM 要 kebab-case(`background-color`)。
/// 命名差异由这一层吸收 —— 上层只需要写一种拼法。
pub fn css_property_name(key : String) -> String {
let buf = StringBuilder::new()
for c in key {
if c >= 'A' && c <= 'Z' {
buf.write_char('-')
buf.write_char(Char::from_int(c.to_int() + 32))
} else {
buf.write_char(c)
}
}
buf.to_string()
}