2 个版本
0.1.3 | 2023 年 1 月 12 日 |
---|---|
0.1.0 | 2022 年 11 月 3 日 |
#2048 in 网页编程
32KB
209 行
rusty-css
在 Rust 中实现 CSS 的一种锈迹斑斑的方式
rusty-css 提供了一种以熟悉的方式创建和导出 CSS 样式的解决方案,但无需离开 Rust 语法。您可以逐个访问和操作您定义的每个值。
标识符按原样取用并转换为字符串,唯一的例外是下划线令牌 (_),当结构体要转换为相应的 CSS 值时,它会被转换为减号令牌 (-)。
示例
struct A {
css_property: "css value"
}
将被转换为
"css-property: css value"
无论属性名称或值的有效性如何。如果您的 CSS 中有错误,它仍然可以编译!
路线图
- 将 Rust 结构体渲染为内联 CSS 代码
- 支持深层嵌套
- 从内联 CSS 设置结构体的值
- 更可靠地从 String 中提取数值
- 支持类
- 支持查询
- 在编译时验证编写的 CSS 代码
- 根据 CSS 规范自动实现默认样式结构体
- 具有严格类型(例如,为给定属性的每个可能单位使用枚举)的第二层系统实现
- 更少的样板代码,更多抽象
如何使用
目前,此包使用 bevy_reflect crate 将结构体转换为 CSS "属性:值" 字符串,因此您希望转换的所有结构体都必须派生自 Reflect
use bevy_reflect::{Reflect};
#[derive(Reflect)]
struct ExampleStruct {
width: String,
height: String,
background: String,
transform: String,
}
要将此结构体转换为内联 CSS 字符串,我们首先需要实现该结构体及其初始状态。为此,我们实现此包中的 Style Trait。
use rusty_css::*;
impl Style for ExampleStruct {
fn create() -> Self {
// return an instance of Self, so in this case an instance of ExampleStruct
Self {
width: "4em".to_string(),
height: "2rem".to_string(),
background: "rgb(69,13,37)".to_string(),
transform: "skewX(20deg) skewY(30deg)".to_string(),
}
}
}
现在我们可以创建一个 ExampleStruct 实例并将其转换为 CSS 内联字符串。
let example_struct = ExampleStruct::create();
let inline_css: String = example_struct.inline();
// "width: 4em; height: 2rem; background: rgb(69,13,37); transform: skewX(20deg) skewY(30deg);"
开发者体验改进
由于可能很难访问可以接受多个值(如 transform)的属性值,我们可以在原始结构体中实现嵌套结构体。通过这样做,第二层结构体的字段不再被视为 CSS 属性,而是作为接受参数的 CSS 函数。
#[allow(non_snake_case)] // so the skewX field doesn't throw a warning for being in snake case, which css uses
#[derive(Reflect)]
struct NestedTransformStruct {
skewX: String,
skewY: String,
}
#[derive(Reflect)]
struct ExampleStruct {
width: String,
height: String,
background: String,
transform: NestedTransformStruct,
}
impl Style for ExampleStruct {
fn create() -> Self {
// return an instance of Self, so in this case an instance of ExampleStruct
Self {
width: "4em".to_string(),
height: "2rem".to_string(),
background: "rgb(69,13,37)".to_string(),
transform: NestedTransformStruct {
skewX: "20deg".to_string(),
skewY: "30deg".to_string(),
},
}
}
}
let example_struct = ExampleStruct::create();
let inline_css: String = example_struct.inline();
let skewX: String = example_struct.transform.skewX; // can access this field, wuhu!
// "width: 4em; height: 2rem; background: rgb(69,13,37); transform: skewX(20deg) skewY(30deg);"
输出相同,但现在我们可以单独访问 skewX 和 skewY 字段。按照这个逻辑,您应该能够以类似的方式编写背景字段的值,让我们试试
#[derive(Reflect)]
struct Background {
rgb: String,
}
#[derive(Reflect)]
struct ExampleStruct {
width: String,
height: String,
background: Background,
}
impl Style for ExampleStruct {
fn create() -> Self {
// return an instance of Self, so in this case an instance of ExampleStruct
Self {
width: "4em".to_string(),
height: "2rem".to_string(),
background: Background {
rgb: "69,13,37".to_string(),
}
}
}
}
let example_struct = ExampleStruct::create();
let inline_css: String = example_struct.inline();
// "width: 4em; height: 2rem; background: rgb(69,13,37);"
一切正常!
您可能已经注意到我们正在附加很多 .to_string() 调用。在扩展规模时,这可能会变得相当繁琐,因此我创建了 append_to_string crate,它有助于解决这个问题。
完整示例
解决所有这些问题后,下面是您的代码可能的样子
use rusty_css::*;
use append_to_string::*;
use bevy_reflect::{Reflect};
// define all the structs we want to be css-ified
#[allow(non_snake_case)]
#[derive(Reflect)]
struct NestedTransformStruct {
skewX: String,
skewY: String,
}
#[derive(Reflect)]
struct ExampleStruct {
width: String,
height: String,
background: String,
transform: NestedTransformStruct,
}
impl Style for ExampleStruct {
fn create() -> Self {
// return an instance of Self, so in this case an instance of ExampleStruct
append_to_string!(
Self {
width: "4em",
height: "2rem",
background: "rgb(69,13,37)",
transform: NestedTransformStruct {
skewX: "20deg",
skewY: "30deg",
},
}
)
}
}
let example_struct = ExampleStruct::create();
let inline_css: String = example_struct.inline();
// "width: 4em; height: 2rem; background: rgb(69,13,37); transform: skewX(20deg) skewY(30deg);"
将样式实现为类
使用您的结构体的 .as_class()
函数来将其样式导出到应用中的 <style>
标签。目前,您需要传递您想要导出样式的 web_sys::Document
的引用,如下所示
let style_struct = ExampleStruct::create();
// grab the current document
let window = web_sys::window().expect("No global `window` found");
let document = window.document().expect("couldn't get `document");
// export the style to the <style> tag
let style_struct = ExampleStruct::create();
let class_name = style_struct.as_class(&document).unwrap();
assert_eq!("ExampleStruct", class_name); //true
在一个 yew 组件中,它可能看起来像这样
fn view(&self, ctx: &Context<Self>) -> Html {
let window = window().expect("No global `window` found");
let document = window.document().expect("couldn't get `document");
let class_name = self.style.as_class(&document).unwrap();
html! {
<div class={class_name}
</div>
}
}
支持类型
以下结构体将用作嵌套结构体的示例
#[derive(Reflect, FromReflect)]
struct EvenFurtherNestedStruct {
further_vec_field: Vec<String>,
}
#[derive(Reflect, FromReflect)]
struct NestedStruct {
nested_vec_field: Vec<String>,
nested_string_field: String,
nested_struct_field: EvenFurtherNestedStruct,
}
注意,如果您想要使用结构体向量,结构体除了 Reflect
之外还必须派生 FromReflect
#[derive(Reflect)]
struct Struct {
string_field: String,
//string-field: String;
struct_field: NestedStruct,
//struct-field:
// nested-string-field(String)
// nested-vec-field(
// String1,
// String2,
// String3,
// String...
// )
// nested-struct-field(
// further-vec-field(
// String1,
// String2,
// String...
// )
// )
//;
vec_field_string: Vec<String>,
//vec-field-string: String1, String2, String...;
vec_field_struct: Vec<NestedStruct>, //
} //vec_field_struct:
// nested-string-field(String)
// nested-vec-field(...)
// nested-struct-field(...)
// ,
// nested-string-field(String)
// nested-vec-field(...)
// nested-struct-field(...)
// ,
// ...
//;
嵌套结构体在实现类似渐变背景等功能时非常有用
background-image: radial-gradient(rgb(0,46,255), rgb(0,255,64), rgb(255,255,0));
在 Rust 中手动实现类似此功能,并且能够直接访问值,可能看起来像这样
#[derive(Reflect, FromReflect)]
struct RGB {
rgb: Vec<String>
}
#[derive(Reflect)]
struct RG {
radial_gradient: Vec<RGB>
}
#[derive(Reflect)]
struct BG {
background_image: RG,
}
impl Style for BG {
fn create() -> Self {
Self {
background_image: RG {
radial_gradient: vec![
RGB {
rgb: vec![
"0".to_string(),
"46".to_string(),
"255".to_string()
]
},
RGB {
rgb: vec![
"0".to_string(),
"255".to_string(),
"64".to_string()
]
},
RGB {
rgb: vec![
"255".to_string(),
"255".to_string(),
"0".to_string()
]
},
]
}
}
}
}
如果您这样做,您可以像这样访问单个值
fn main() {
let bg = BG::create(); // create struct
let rgbs = bg.background_image.radial_gradient; // get vec of rgbs
let first_color = rgbs[0] // get the first color of the gradient
first_color[0] = 255 // change the value of the red channel of the first color of the gradient
}
从 CSS 中直接获取值
您还可以使用 set_from_inline_string()
方法从 CSS 字符串设置任何上述结构体的值。但是,您必须小心匹配 CSS 字符串与您的结构体结构。例如,您可以在 tests/from_string.rs
文件中查看示例。
包实现
trait ExtractNums {
// tries to extract the numbers from whatever string is given.
fn try_extract_nums(&self) -> Option<String>;
// tries to cast any given string to an f64
fn try_to_f64(&self) -> Result<f64, ParseFloatError>;
}
trait Style {
// returns the inline css representaton of a struct
fn inline(&self) -> String
// Sets a structs' fields' values equivalent to the given inline css string
// Struct { struct_field: "value" }.set_from_inline_string("struct-field: different value");
// will result in:
// Struct { struct_field: "different value"}
// (also works for nested structs)
fn set_from_inline_string(&self, style: String) -> &Self
// - returns the class name to put into the class attribute
// - inserts the style as a class into the style sheet
fn as_class(&mut self, document: &Document) -> Result<String, &'static str> ;
// retruns the struct as a css class String like so: .StructIdent { property: value }
fn as_class_string(&mut self, mut class_name: String) -> Result<String, &'static str>;
// returns the struct name
fn get_struct_name(&self) -> Result<String, &'static str>;
// logs the Reflects of the given objects fields to the browser console with wasm_logger
fn debug(self) -> Self;
//----------internal funcs----------//
// retruns the String Representation of a fields value
fn create_value_string(reflect: &dyn Reflect) -> String;
// sets the value of a String field to the value side of a property in a css string (e.g. prop: >value<)
fn set_string_reflect(string_reflect: &mut dyn Reflect, value: &str);
// sets the value of a Struct field to the value side of a property in a css string (e.g. prop: >field(value)<)
fn set_struct_reflect(struct_reflect: &mut dyn Struct, value: &str);
// sets the value of a Vec (bevy_reflect Lists in general) field to the value side of a property in a css string (e.g. prop: >1,2,3,4<)
fn set_list_reflect(list_reflect: &mut dyn List, value: &str)
}
保留字段名称
append: "this will be appended to the class name" // for classes that are supposed to be exported with a psuedo-class (i.e. :before, :active, etc.)
依赖关系
~14–23MB
~322K SLoC