├── .github └── workflows │ └── mdbook.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── Cargo.toml ├── impl_style.rs ├── props_macro.rs ├── simple_window.rs ├── style_macro.rs ├── window.rs └── window_backend.rs ├── images ├── banner_with_shadow.jpg ├── banner_with_shadow_mirrored.jpg ├── irisia_new.jpg └── window.gif ├── irisia-backend ├── Cargo.toml └── src │ ├── application.rs │ ├── lib.rs │ ├── render_window │ ├── mod.rs │ ├── renderer.rs │ └── window.rs │ ├── runtime │ ├── global_event.rs │ ├── mod.rs │ └── rt_event.rs │ └── window_handle │ ├── close_handle.rs │ ├── create.rs │ └── mod.rs ├── irisia-book ├── book.toml ├── custom.css └── src │ ├── SUMMARY.md │ ├── banner.png │ ├── build_syntax │ ├── case_structure.md │ ├── index.md │ ├── repetitive_structure.md │ └── seq_nest_and_cmd.md │ ├── contributing.md │ ├── get_started │ ├── first_window.md │ ├── installation.md │ └── on_windows.md │ ├── index.md │ ├── irisia_and_me.md │ ├── principle.ai │ ├── principle.md │ ├── principle.png │ ├── style_syntax │ ├── animation.md │ └── index.md │ └── why_irisia_gui.md ├── irisia-macros ├── Cargo.toml └── src │ ├── dep_search │ ├── macro_dep_guessing.rs │ └── mod.rs │ ├── derive_props │ ├── attrs │ │ ├── mod.rs │ │ ├── parse_field_attr.rs │ │ └── parse_struct_attr.rs │ ├── impl_miscellaneous.rs │ ├── impl_update_with.rs │ └── mod.rs │ ├── derive_style │ ├── attributes │ │ ├── mod.rs │ │ └── parse_paths.rs │ ├── codegen_utils │ │ ├── append_path.rs │ │ ├── impl_default.rs │ │ └── mod.rs │ ├── derive_enum.rs │ ├── derive_struct.rs │ ├── mod.rs │ └── variant_analyzer │ │ ├── full_quality_paths.rs │ │ ├── mod.rs │ │ └── read_fields.rs │ ├── derive_style_reader.rs │ ├── element │ ├── build.rs │ ├── mod.rs │ └── stmt │ │ ├── mod.rs │ │ ├── parse.rs │ │ └── to_tokens.rs │ ├── expr │ ├── conditional │ │ ├── mod.rs │ │ ├── state_if.rs │ │ └── state_match.rs │ ├── mod.rs │ ├── repetitive │ │ ├── mod.rs │ │ ├── state_for.rs │ │ └── state_while.rs │ ├── state_block.rs │ ├── state_command.rs │ └── state_expr.rs │ ├── inner_impl_listen.rs │ ├── lib.rs │ ├── main_macro.rs │ └── style │ ├── mod.rs │ └── stmt.rs ├── irisia-utils ├── Cargo.toml └── src │ ├── lib.rs │ └── reuse_vec.rs ├── irisia-widgets ├── Cargo.toml └── src │ ├── box_styles │ ├── border.rs │ ├── border_clip.rs │ ├── border_radius.rs │ ├── box_shadow.rs │ ├── box_style_renderer.rs │ ├── margin.rs │ ├── mod.rs │ └── padding.rs │ ├── lib.rs │ ├── textbox │ ├── mod.rs │ ├── selection.rs │ └── styles.rs │ └── textbox_legacy │ ├── mod.rs │ ├── selection.rs │ └── styles.rs └── irisia ├── Cargo.toml └── src ├── application ├── backend.rs ├── content.rs ├── event_comp │ ├── global │ │ ├── focusing.rs │ │ ├── mod.rs │ │ └── new_event.rs │ ├── mod.rs │ └── node.rs ├── mod.rs └── redraw_scheduler.rs ├── dom ├── children │ ├── children_node.rs │ ├── mod.rs │ └── render_multiple.rs ├── data_structure.rs ├── drop_protection.rs ├── layer │ ├── mod.rs │ ├── queue.rs │ └── rebuild.rs ├── mod.rs ├── pub_handle │ ├── layout_el.rs │ ├── mod.rs │ └── write_guard.rs └── update.rs ├── element ├── mod.rs ├── props │ ├── defaulter.rs │ ├── help_create.rs │ ├── help_update.rs │ ├── mod.rs │ └── set_std_styles.rs └── render_element.rs ├── event ├── event_dispatcher │ ├── extension.rs │ ├── lock.rs │ ├── maybe_confirmed.rs │ ├── mod.rs │ ├── receive.rs │ └── scheduler │ │ ├── mod.rs │ │ └── stock.rs ├── listen.rs ├── metadata.rs ├── mod.rs └── standard │ ├── mod.rs │ └── window_event.rs ├── lib.rs ├── log └── mod.rs ├── primitive ├── mod.rs ├── pixel.rs └── point.rs ├── private ├── chain_caller.rs ├── for_loop.rs └── mod.rs ├── prop_test.rs ├── structure ├── branch.rs ├── chain.rs ├── empty.rs ├── mod.rs ├── once.rs ├── repeat.rs └── slot.rs ├── style ├── branch.rs ├── chain.rs ├── mod.rs ├── once.rs ├── reader.rs └── style_box.rs └── update_with.rs /.github/workflows/mdbook.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a mdBook site to GitHub Pages 2 | # 3 | # To get started with mdBook see: https://rust-lang.github.io/mdBook/index.html 4 | # 5 | name: Deploy mdBook site to Pages 6 | 7 | on: 8 | # Runs on pushes targeting the default branch 9 | push: 10 | branches: ["main"] 11 | paths: 12 | - "irisia-book/**" 13 | 14 | # Allows you to run this workflow manually from the Actions tab 15 | workflow_dispatch: 16 | 17 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 18 | permissions: 19 | contents: read 20 | pages: write 21 | id-token: write 22 | 23 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 24 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 25 | concurrency: 26 | group: "pages" 27 | cancel-in-progress: false 28 | 29 | jobs: 30 | # Build job 31 | build: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v3 35 | - name: Install mdBook 36 | run: | 37 | curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh 38 | rustup update 39 | cargo install mdbook 40 | - name: Setup Pages 41 | id: pages 42 | uses: actions/configure-pages@v3 43 | - name: Build with mdBook 44 | run: mdbook build irisia-book -d book 45 | - name: Upload artifact 46 | uses: actions/upload-pages-artifact@v1 47 | with: 48 | path: ./irisia-book/book 49 | 50 | # Deployment job 51 | deploy: 52 | environment: 53 | name: github-pages 54 | url: ${{ steps.deployment.outputs.page_url }} 55 | runs-on: ubuntu-latest 56 | needs: build 57 | steps: 58 | - name: Deploy to GitHub Pages 59 | id: deployment 60 | uses: actions/deploy-pages@v2 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /irisia-book/book 4 | /index.html 5 | /.VSCodeCounter -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "(Windows) Launch", 6 | "type": "cppvsdbg", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/target/debug/examples/simple_window.exe", 9 | "args": [], 10 | "stopAtEntry": false, 11 | "cwd": "${workspaceRoot}", 12 | "environment": [], 13 | "externalConsole": true 14 | }, 15 | { 16 | "name": "(OSX) Launch", 17 | "type": "lldb", 18 | "request": "launch", 19 | "program": "${workspaceRoot}/target/debug/foo", 20 | "args": [], 21 | "cwd": "${workspaceRoot}", 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.showUnlinkedFileNotification": false, 3 | "GitCommitPlugin.CustomCommitType": [ 4 | { 5 | "key": "api", 6 | "label": "api", 7 | "detail": "重构后更改了公共或私有的API", 8 | "icon": "🔄" 9 | } 10 | ], 11 | "rust-analyzer.cargo.features": [], 12 | "rust-analyzer.cargo.noDefaultFeatures": true 13 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "irisia-widgets", 5 | "irisia", 6 | "irisia-backend", 7 | "irisia-macros", 8 | "irisia-utils", 9 | "examples", 10 | ] 11 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | # [[example]] 8 | # name = "window" 9 | # path = "window.rs" 10 | 11 | [[example]] 12 | name = "props_macro" 13 | path = "props_macro.rs" 14 | 15 | [[example]] 16 | name = "simple_window" 17 | path = "simple_window.rs" 18 | 19 | [dev-dependencies] 20 | tokio = { version = "1.27", features = ["sync", "rt-multi-thread", "macros"] } 21 | irisia = { path = "../irisia" } 22 | irisia_widgets = { path = "../irisia-widgets" } 23 | -------------------------------------------------------------------------------- /examples/impl_style.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use irisia_macros::Style; 4 | 5 | fn main() {} 6 | 7 | #[derive(Style, Clone)] 8 | enum MyStyle { 9 | #[irisia(from)] 10 | XY { x: u32, y: u32 }, 11 | 12 | #[irisia(from)] 13 | Quant { 14 | left: u32, 15 | top: u32, 16 | right: u32, 17 | bottom: u32, 18 | }, 19 | } 20 | 21 | #[derive(Style, Clone)] 22 | #[irisia(from = "t, l[, b, r] | l")] 23 | struct StructualStyle { 24 | l: u32, 25 | 26 | #[irisia(default)] 27 | r: u32, 28 | 29 | #[irisia(default)] 30 | t: u32, 31 | 32 | #[irisia(default)] 33 | b: u32, 34 | } 35 | -------------------------------------------------------------------------------- /examples/props_macro.rs: -------------------------------------------------------------------------------- 1 | use irisia::{ 2 | element::props::{PropsUpdateWith, SetStdStyles}, 3 | style::StyleColor, 4 | }; 5 | 6 | #[irisia::props( 7 | vis = "pub", 8 | updater = "After", 9 | watch(group = "all_unchanged", exclude = "will_change") 10 | )] 11 | #[allow(unused)] 12 | struct Origin { 13 | #[props(watch, must_init, updated)] 14 | will_change: String, 15 | 16 | #[props( 17 | watch = "wont_chaaange_unchanged", 18 | default = r#""unknown".into()"#, 19 | updated 20 | )] 21 | wont_change: String, 22 | 23 | #[props(read_style(stdin))] 24 | abcd: Option, 25 | } 26 | 27 | fn main() { 28 | let mut origin = Origin::props_create_with( 29 | After::default() 30 | .will_change("Doge") 31 | .wont_change("this field will not change") 32 | .set_std_styles(&()), 33 | ); 34 | 35 | let result = origin.props_update_with(After::default().will_change("Cats").set_std_styles(&())); 36 | 37 | assert!(!result.will_change_unchanged); 38 | assert!(result.wont_chaaange_unchanged); 39 | assert!(result.all_unchanged); 40 | } 41 | -------------------------------------------------------------------------------- /examples/simple_window.rs: -------------------------------------------------------------------------------- 1 | use irisia::{ 2 | application::Window, 3 | build, 4 | element::{Element, ElementUpdate}, 5 | event::standard::{CloseRequested, PointerDown}, 6 | skia_safe::Color, 7 | style, 8 | style::StyleColor, 9 | ElModel, 10 | }; 11 | use irisia_widgets::textbox::{ 12 | styles::{StyleFontSize, StyleFontWeight}, 13 | TextBox, 14 | }; 15 | use window_backend::{Flex, Rectangle, StyleHeight, StyleWidth}; 16 | 17 | mod window_backend; 18 | 19 | #[irisia::main] 20 | async fn main() { 21 | Window::new::("hello irisia") 22 | .await 23 | .unwrap() 24 | .join() 25 | .await; 26 | } 27 | 28 | struct App { 29 | rects: Vec>, 30 | } 31 | 32 | impl Element for App { 33 | type BlankProps = (); 34 | 35 | fn set_children(&self, this: &ElModel!()) { 36 | this.set_children(build! { 37 | Flex { 38 | TextBox { 39 | text: "Hello\nпpивeт\nこんにちは\n你好\n\nIrisia GUI🌺", 40 | user_select: true, 41 | +style: style! { 42 | if 1 + 1 == 2 { 43 | color: Color::MAGENTA; 44 | } 45 | font_weight: .bold; 46 | font_size: 30px; 47 | } 48 | } 49 | 50 | for (index, color) in self.rects.iter().enumerate() { 51 | @key index; 52 | if let Some(color) = color { 53 | Rectangle { 54 | +style: style! { 55 | width: 100px; 56 | height: 100px + 40px * index as f32; 57 | color: *color; 58 | }, 59 | +oncreate: move |em| { 60 | rect_rt(this, em, index); 61 | }, 62 | } 63 | } 64 | } 65 | } 66 | }) 67 | .layout_once(this.draw_region()) 68 | .unwrap(); 69 | } 70 | } 71 | 72 | impl ElementUpdate<()> for App { 73 | fn el_create(this: &ElModel!(), _: ()) -> Self { 74 | this.global() 75 | .event_dispatcher() 76 | .listen() 77 | .no_handle() 78 | .spawn(|cr: CloseRequested| { 79 | println!("close requsted"); 80 | cr.0.close(); 81 | }); 82 | 83 | Self { 84 | rects: vec![ 85 | Some(Color::RED), 86 | Some(Color::YELLOW), 87 | Some(Color::BLUE), 88 | Some(Color::GREEN), 89 | Some(Color::BLACK), 90 | ], 91 | } 92 | } 93 | 94 | fn el_update(&mut self, _: &ElModel!(), _: (), _: bool) -> bool { 95 | true 96 | } 97 | } 98 | 99 | fn rect_rt(this: &ElModel!(App), rect: &ElModel!(Rectangle), index: usize) { 100 | println!("rectangle {index} got"); 101 | let this = this.clone(); 102 | rect.listen() 103 | .trusted() 104 | .no_handle() 105 | .once() 106 | .asyn() 107 | .spawn(move |_: PointerDown| async move { 108 | println!("rectangle {} deleted", index); 109 | this.el_write().await.unwrap().rects[index].take(); 110 | }); 111 | } 112 | -------------------------------------------------------------------------------- /examples/style_macro.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use irisia_core::primitive::Pixel; 4 | use irisia_core::style::AddStyle; 5 | use irisia_core::style::NoStyle; 6 | use irisia_core::style::Style; 7 | use irisia_core::style::StyleContainer; 8 | use irisia_macros::style; 9 | 10 | fn main() { 11 | type ExtStyle = irisia_core::style::Chain< 12 | irisia_core::style::Chain>, 13 | AddStyle, 14 | >; 15 | 16 | let ext_style = style! { 17 | style2: false; 18 | style1: "looo"; 19 | }; 20 | 21 | let my_str = Some("hello world"); 22 | style! { 23 | style1: 10, 10; 24 | 25 | if 1 + 1 == 2 { 26 | style2: true; 27 | } 28 | 29 | match my_str { 30 | None => { 31 | style1: 10, 20; 32 | @extend ext_style; 33 | } 34 | 35 | Some("ddd")=>{}, 36 | 37 | Some(other) => { 38 | style1: &other[..5]; 39 | pixel_test: 2px, 6.2px, .scale 10.2, .empty; 40 | } 41 | } 42 | }; 43 | } 44 | 45 | #[derive(Clone)] 46 | struct StyleStyle1; 47 | 48 | impl Style for StyleStyle1 {} 49 | 50 | impl From<(u32, u32)> for StyleStyle1 { 51 | fn from(_: (u32, u32)) -> Self { 52 | Self 53 | } 54 | } 55 | 56 | impl From<(&'static str,)> for StyleStyle1 { 57 | fn from(_: (&'static str,)) -> Self { 58 | Self 59 | } 60 | } 61 | 62 | #[derive(Clone)] 63 | struct StyleStyle2; 64 | 65 | impl Style for StyleStyle2 {} 66 | 67 | impl From<(bool,)> for StyleStyle2 { 68 | fn from(_: (bool,)) -> Self { 69 | Self 70 | } 71 | } 72 | 73 | #[derive(Clone)] 74 | struct StylePixelTest; 75 | 76 | impl Style for StylePixelTest {} 77 | 78 | impl From<(Pixel,)> for StylePixelTest { 79 | fn from(_value: (Pixel,)) -> Self { 80 | Self 81 | } 82 | } 83 | 84 | impl From<(Pixel, Pixel)> for StylePixelTest { 85 | fn from(_value: (Pixel, Pixel)) -> Self { 86 | Self 87 | } 88 | } 89 | 90 | impl StylePixelTest { 91 | fn empty(&mut self) {} 92 | fn scale(&mut self, _mul: f32) {} 93 | } 94 | -------------------------------------------------------------------------------- /examples/window.rs: -------------------------------------------------------------------------------- 1 | //#![windows_subsystem = "windows"] 2 | use irisia::{ 3 | application::{CloseHandle, Window}, 4 | box_styles::*, 5 | build, 6 | element::{Element, EventHandle, InitContent, NeverInitalized, NoProps}, 7 | event::standard::{Blured, Click, Focused}, 8 | skia_safe::Color, 9 | structure_legacy::StructureBuilder, 10 | style, 11 | style::StyleColor, 12 | textbox_legacy::{styles::*, TextBox}, 13 | }; 14 | use window_backend::{Flex, MyRequestClose, Rectangle, StyleHeight, StyleWidth}; 15 | 16 | mod window_backend; 17 | 18 | #[irisia::main] 19 | async fn main() { 20 | let win = Window::new::("hello irisia").await; 21 | println!("window recv"); 22 | win.unwrap().join().await; 23 | } 24 | 25 | struct App { 26 | rects: Vec, 27 | } 28 | 29 | impl Element for App { 30 | type Props<'a> = NoProps; 31 | type ChildProps<'a> = NeverInitalized; 32 | 33 | fn render<'a>( 34 | &mut self, 35 | mut frame: irisia::Frame< 36 | Self, 37 | impl style::StyleContainer, 38 | impl irisia::structure_legacy::VisitIter>, 39 | >, 40 | ) -> irisia::Result<()> { 41 | build! { 42 | Flex { 43 | TextBox { 44 | text: "Hello\npивeт\nこんにちは\n你好\n\nIrisia GUI🌺", 45 | user_select: true, 46 | +style: style! { 47 | if 1 + 1 == 2 { 48 | color: Color::MAGENTA; 49 | ~: Color::MAGENTA; 50 | } 51 | border: 5px, Color::GREEN, .round_cap; 52 | border_radius: 30px; 53 | box_shadow: 20px, Color::MAGENTA; 54 | margin: .left 20px, .top 30px; 55 | font_weight: .bold; 56 | font_size: 30px; 57 | }, 58 | +oncreate: move |eh| { 59 | eh.spawn(textbox_rt(eh.clone())); 60 | } 61 | } 62 | 63 | for (index, color) in self.rects.iter().enumerate() { 64 | @key index; 65 | Rectangle { 66 | +style: style!{ 67 | width: 100.0; 68 | height: 100.0 + 40.0 * index as f32; 69 | color: color.clone(); 70 | }, 71 | +oncreate: move |eh| { 72 | let eh_cloned = eh.clone(); 73 | eh.spawn(rect_rt(eh_cloned, frame.ri.close_handle, index)); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | .into_rendering(&mut frame.content) 80 | .finish(frame.drawing_region) 81 | } 82 | 83 | fn create(_: &InitContent) -> Self { 84 | Self { 85 | rects: vec![ 86 | Color::RED, 87 | Color::YELLOW, 88 | Color::BLUE, 89 | Color::GREEN, 90 | Color::BLACK, 91 | ], 92 | } 93 | } 94 | } 95 | 96 | async fn rect_rt(eh: EventHandle, close_handle: CloseHandle, key: usize) { 97 | println!("rectangle {} got!", key); 98 | 99 | eh.listen() 100 | .recv_sys() 101 | .spawn(move |_: Focused| println!("rectangle {} gained focus", key)); 102 | 103 | eh.listen() 104 | .recv_sys() 105 | .spawn(move |_: Blured| println!("rectangle {} lost focus", key)); 106 | 107 | eh.listen() 108 | .recv_sys() 109 | .spawn(move |_: Click| println!("rectangle {} clicked", key)); 110 | 111 | eh.listen().spawn(move |_: MyRequestClose, _| { 112 | println!("close request event received(sent by {})", key); 113 | close_handle.close(); 114 | }); 115 | } 116 | 117 | async fn textbox_rt(eh: EventHandle) { 118 | loop { 119 | eh.hover().await; 120 | println!("cursor hovering on textbox"); 121 | eh.hover_canceled().await; 122 | println!("cursor hovering canceled"); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /images/banner_with_shadow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fancyflame/irisia-gui/9f98a6ce5f14ae1ae18295bb041000c370b43c35/images/banner_with_shadow.jpg -------------------------------------------------------------------------------- /images/banner_with_shadow_mirrored.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fancyflame/irisia-gui/9f98a6ce5f14ae1ae18295bb041000c370b43c35/images/banner_with_shadow_mirrored.jpg -------------------------------------------------------------------------------- /images/irisia_new.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fancyflame/irisia-gui/9f98a6ce5f14ae1ae18295bb041000c370b43c35/images/irisia_new.jpg -------------------------------------------------------------------------------- /images/window.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fancyflame/irisia-gui/9f98a6ce5f14ae1ae18295bb041000c370b43c35/images/window.gif -------------------------------------------------------------------------------- /irisia-backend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "irisia_backend" 3 | version = "1.0.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | winit = { version = "0.28", features = ["android-native-activity"] } 10 | pixels = "0.12" 11 | skia-safe = { version = "0.66", features = ["textlayout", "binary-cache"] } 12 | tokio = { version = "1.28", features = ["rt-multi-thread"] } 13 | anyhow = "1" 14 | lazy_static = "1" 15 | futures = "0.3" 16 | 17 | [target.'cfg(target_os = "android")'.dependencies] 18 | android_logger = "0.11.0" 19 | 20 | [features] 21 | fps_recorder = [] 22 | -------------------------------------------------------------------------------- /irisia-backend/src/application.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use anyhow::Result; 4 | use skia_safe::Canvas; 5 | 6 | pub trait AppWindow: 'static { 7 | fn on_redraw(&mut self, canvas: &mut Canvas, delta: Duration) -> Result<()>; 8 | fn on_window_event(&mut self, event: crate::StaticWindowEvent); 9 | fn on_destroy(&mut self); 10 | } 11 | -------------------------------------------------------------------------------- /irisia-backend/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod application; 2 | mod render_window; 3 | pub mod runtime; 4 | pub mod window_handle; 5 | 6 | pub use application::AppWindow; 7 | pub use runtime::start_runtime; 8 | 9 | pub use skia_safe; 10 | pub use winit; 11 | 12 | pub type WinitWindow = winit::window::Window; 13 | 14 | // only for export, shouldn't use in crate, which may cause confusion 15 | pub type StaticWindowEvent = winit::event::WindowEvent<'static>; 16 | -------------------------------------------------------------------------------- /irisia-backend/src/render_window/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Condvar, Mutex as StdMutex}; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use tokio::{sync::mpsc, task::LocalSet}; 5 | 6 | use crate::{runtime::rt_event::AppBuildFn, StaticWindowEvent, WinitWindow}; 7 | 8 | use self::window::RenderWindow; 9 | 10 | mod renderer; 11 | mod window; 12 | 13 | enum Command { 14 | Redraw, 15 | HandleEvent(StaticWindowEvent), 16 | } 17 | 18 | pub struct RenderWindowController { 19 | chan: mpsc::UnboundedSender, 20 | draw_finished: Arc<(StdMutex, Condvar)>, 21 | } 22 | 23 | impl RenderWindowController { 24 | pub fn new(app: AppBuildFn, window: Arc) -> Self { 25 | let (tx, mut rx) = mpsc::unbounded_channel::(); 26 | let draw_finished = Arc::new((StdMutex::new(true), Condvar::new())); 27 | let draw_finished_cloned = draw_finished.clone(); 28 | 29 | std::thread::Builder::new() 30 | .name("irisia window".into()) 31 | .spawn(move || { 32 | let async_runtime = tokio::runtime::Builder::new_current_thread() 33 | .enable_all() 34 | .build() 35 | .unwrap(); 36 | 37 | let local = LocalSet::new(); 38 | local.block_on(&async_runtime, async move { 39 | let mut rw = RenderWindow::new(app, window).expect("cannot launch renderer"); 40 | loop { 41 | let Some(cmd) = rx.recv().await 42 | else { 43 | break; 44 | }; 45 | 46 | match cmd { 47 | Command::Redraw => { 48 | rw.redraw(); 49 | *draw_finished.0.lock().unwrap() = true; 50 | draw_finished.1.notify_all(); 51 | } 52 | Command::HandleEvent(ev) => rw.handle_event(ev), 53 | } 54 | } 55 | }); 56 | }) 57 | .unwrap(); 58 | 59 | Self { 60 | chan: tx, 61 | draw_finished: draw_finished_cloned, 62 | } 63 | } 64 | 65 | pub fn redraw(&self) -> Result<()> { 66 | let mut finished = self.draw_finished.0.lock().unwrap(); 67 | *finished = false; 68 | self.chan 69 | .send(Command::Redraw) 70 | .map_err(|_| recv_shut_down_error())?; 71 | 72 | while !*finished { 73 | finished = self.draw_finished.1.wait(finished).unwrap(); 74 | } 75 | Ok(()) 76 | } 77 | 78 | pub fn handle_event(&self, event: StaticWindowEvent) -> Result<()> { 79 | self.chan 80 | .send(Command::HandleEvent(event)) 81 | .map_err(|_| recv_shut_down_error()) 82 | } 83 | } 84 | 85 | fn recv_shut_down_error() -> anyhow::Error { 86 | anyhow!("worker unexpectedly shutted down") 87 | } 88 | -------------------------------------------------------------------------------- /irisia-backend/src/render_window/window.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::Arc, 3 | time::{Duration, Instant}, 4 | }; 5 | 6 | use anyhow::Result; 7 | 8 | use crate::{runtime::rt_event::AppBuildFn, AppWindow, StaticWindowEvent, WinitWindow}; 9 | 10 | use super::renderer::Renderer; 11 | 12 | pub struct RenderWindow { 13 | app: Box, 14 | window: Arc, 15 | renderer: Renderer, 16 | last_frame_instant: Option, 17 | } 18 | 19 | impl RenderWindow { 20 | pub fn new(app: AppBuildFn, window: Arc) -> Result { 21 | Ok(RenderWindow { 22 | app: app(), 23 | renderer: Renderer::new(&window)?, 24 | window, 25 | last_frame_instant: None, 26 | }) 27 | } 28 | 29 | pub fn redraw(&mut self) { 30 | let delta = { 31 | let now = Instant::now(); 32 | match self.last_frame_instant.replace(now) { 33 | Some(last) => now.duration_since(last), 34 | None => Duration::MAX, 35 | } 36 | }; 37 | 38 | if let Err(err) = self.renderer.resize(self.window.inner_size()) { 39 | eprintln!("cannot resize window: {err}"); 40 | } 41 | 42 | if let Err(err) = self 43 | .renderer 44 | .render(|canvas| self.app.on_redraw(canvas, delta)) 45 | { 46 | eprintln!("render error: {err}"); 47 | } 48 | } 49 | 50 | pub fn handle_event(&mut self, event: StaticWindowEvent) { 51 | self.app.on_window_event(event); 52 | } 53 | } 54 | 55 | impl Drop for RenderWindow { 56 | fn drop(&mut self) { 57 | self.app.on_destroy(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /irisia-backend/src/runtime/global_event.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use tokio::sync::{Mutex, MutexGuard}; 3 | 4 | use winit::event_loop::EventLoopProxy; 5 | 6 | use super::rt_event::WindowReg; 7 | 8 | lazy_static! { 9 | static ref WINDOW_REGITER: Mutex>> = Mutex::new(None); 10 | } 11 | 12 | pub(crate) struct WindowRegiterMutex(MutexGuard<'static, Option>>); 13 | 14 | impl WindowRegiterMutex { 15 | pub fn init(proxy: EventLoopProxy) { 16 | let mut guard = WINDOW_REGITER 17 | .try_lock() 18 | .expect("lock is unexpected blocked during initializing operation"); 19 | 20 | if guard.replace(proxy).is_some() { 21 | panic!("global event loop has been initialized"); 22 | } 23 | } 24 | 25 | pub async fn lock() -> Self { 26 | WindowRegiterMutex(WINDOW_REGITER.lock().await) 27 | } 28 | 29 | pub fn send(&self, reg: WindowReg) { 30 | self.0 31 | .as_ref() 32 | .expect("event loop not started") 33 | .send_event(reg) 34 | .map_err(|e| format!("{e}")) 35 | .unwrap(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /irisia-backend/src/runtime/rt_event.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use tokio::sync::oneshot; 4 | use winit::{ 5 | error::OsError, 6 | window::{WindowBuilder, WindowId}, 7 | }; 8 | 9 | use crate::{AppWindow, WinitWindow}; 10 | 11 | pub(crate) type AppBuildFn = Box Box + Send>; 12 | 13 | pub(crate) enum WindowReg { 14 | RawWindowRequest { 15 | builder: Box WindowBuilder + Send>, 16 | window_giver: oneshot::Sender>, 17 | }, 18 | 19 | WindowRegister { 20 | app: AppBuildFn, 21 | raw_window: Arc, 22 | }, 23 | 24 | WindowDestroyed(WindowId), 25 | 26 | Exit(i32), 27 | } 28 | -------------------------------------------------------------------------------- /irisia-backend/src/window_handle/close_handle.rs: -------------------------------------------------------------------------------- 1 | use winit::window::WindowId; 2 | 3 | use crate::runtime::{global_event::WindowRegiterMutex, rt_event::WindowReg}; 4 | 5 | #[derive(Clone, Copy)] 6 | pub struct CloseHandle(pub(super) WindowId); 7 | 8 | impl CloseHandle { 9 | pub fn close(&self) { 10 | let window_id = self.0; 11 | tokio::spawn(async move { 12 | WindowRegiterMutex::lock() 13 | .await 14 | .send(WindowReg::WindowDestroyed(window_id)); 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /irisia-backend/src/window_handle/create.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::{ 4 | runtime::{global_event::WindowRegiterMutex, rt_event::WindowReg}, 5 | AppWindow, WinitWindow, 6 | }; 7 | 8 | use anyhow::Result; 9 | use tokio::sync::oneshot; 10 | use winit::window::WindowBuilder; 11 | 12 | use super::{close_handle::CloseHandle, RawWindowHandle}; 13 | 14 | impl RawWindowHandle { 15 | pub async fn create(create_app: F1, wb: F2) -> Result 16 | where 17 | A: AppWindow, 18 | F1: FnOnce(Arc, CloseHandle) -> A + Send + 'static, 19 | F2: FnOnce(WindowBuilder) -> WindowBuilder + Send + 'static, 20 | { 21 | let (window_giver, window_receiver) = oneshot::channel(); 22 | 23 | WindowRegiterMutex::lock() 24 | .await 25 | .send(WindowReg::RawWindowRequest { 26 | builder: Box::new(wb), 27 | window_giver, 28 | }); 29 | 30 | let raw_window = Arc::new( 31 | window_receiver 32 | .await? 33 | .expect("inner error: cannot receive window initializing result from runtime"), 34 | ); 35 | 36 | let raw_window_cloned = raw_window.clone(); 37 | let app = move || { 38 | let window_id = raw_window_cloned.id(); 39 | Box::new(create_app(raw_window_cloned, CloseHandle(window_id))) as Box 40 | }; 41 | 42 | WindowRegiterMutex::lock() 43 | .await 44 | .send(WindowReg::WindowRegister { 45 | app: Box::new(app), 46 | raw_window: raw_window.clone(), 47 | }); 48 | 49 | Ok(RawWindowHandle { 50 | close_handle: CloseHandle(raw_window.id()), 51 | raw_window, 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /irisia-backend/src/window_handle/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ops::Deref, sync::Arc}; 2 | 3 | use crate::WinitWindow; 4 | 5 | pub use winit::window::WindowBuilder; 6 | 7 | pub use self::close_handle::CloseHandle; 8 | 9 | mod close_handle; 10 | mod create; 11 | 12 | #[derive(Clone)] 13 | pub struct RawWindowHandle { 14 | pub raw_window: Arc, 15 | pub close_handle: CloseHandle, 16 | } 17 | 18 | impl Deref for RawWindowHandle { 19 | type Target = WinitWindow; 20 | fn deref(&self) -> &Self::Target { 21 | &self.raw_window 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /irisia-book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["FancyFlame"] 3 | language = "zh" 4 | multilingual = false 5 | src = "src" 6 | title = "Book of Irisia GUI" 7 | 8 | [output.html] 9 | additional-css = ["custom.css"] 10 | -------------------------------------------------------------------------------- /irisia-book/custom.css: -------------------------------------------------------------------------------- 1 | div.tip { 2 | margin: 1em 0; 3 | padding: 1em; 4 | border: 2px solid #0074D9; 5 | border-radius: 4px; 6 | background-color: #E1F5FE; 7 | display: flex; 8 | align-items: center; 9 | } 10 | 11 | div.tip::before { 12 | content: "💡"; 13 | font-size: 1.5em; 14 | margin-right: 0.5em; 15 | color: #0074D9; 16 | } 17 | 18 | div.error { 19 | margin: 1em 0; 20 | padding: 1em; 21 | border: 2px solid #FF4136; 22 | border-radius: 4px; 23 | background-color: #FFDCDC; 24 | display: flex; 25 | align-items: center; 26 | } 27 | 28 | div.error::before { 29 | content: "❌"; 30 | font-size: 1.5em; 31 | margin-right: 0.5em; 32 | color: #FF4136; 33 | } 34 | 35 | div.warning { 36 | margin: 1em 0; 37 | padding: 1em; 38 | border: 2px solid #FF851B; 39 | border-radius: 4px; 40 | background-color: #FFE8CC; 41 | display: flex; 42 | align-items: center; 43 | } 44 | 45 | div.warning::before { 46 | content: "⚠"; 47 | font-size: 1.5em; 48 | margin-right: 0.5em; 49 | color: #FF851B; 50 | } -------------------------------------------------------------------------------- /irisia-book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [简介](index.md) 4 | [为什么选择irisia?](why_irisia_gui.md) 5 | [设计原理](principle.md) 6 | [征集开发者!](contributing.md) 7 | [关于我和Irisia](irisia_and_me.md) 8 | 9 | # 学习和使用 10 | 11 | 1. [安装环境](get_started/installation.md) 12 | - [Windows环境](get_started/on_windows.md) 13 | - [Android环境]() 14 | - [Linux环境]() 15 | 2. [第一个窗口](get_started/first_window.md) 16 | 3. [build!语法](build_syntax/index.md) 17 | - [顺序、嵌套和扩展](build_syntax/seq_nest_and_cmd.md) 18 | - [选择结构](build_syntax/case_structure.md) 19 | - [循环结构](build_syntax/repetitive_structure.md) 20 | 4. [style!语法](style_syntax/index.md) 21 | - [选择结构]() 22 | - [动画](style_syntax/animation.md) 23 | 5. [事件简介]() 24 | - [事件分发器]() 25 | - [接收和发送]() 26 | - [自定义事件]() 27 | 6. [设计一个可复用组件]() 28 | - [获取数据]() 29 | - [样式读取器]() 30 | - [自定义属性]() 31 | - [自定义样式]() 32 | - [构建节点数树模板]() 33 | - [创建运行时]() 34 | - [设计一个已知大小组件]() 35 | - [设计一个布局方式]() 36 | 37 | # 深入了解原理 38 | 39 | 7. [build!和style!做了什么]() 40 | 41 | 8. [事件分发器工作原理]() 42 | - [接收事件]() 43 | - [发送事件]() 44 | - [如何保证不遗漏]() 45 | 46 | 9. [渲染器]() 47 | 48 | # 贡献您的力量 49 | 50 | 10. [加入我们]() -------------------------------------------------------------------------------- /irisia-book/src/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fancyflame/irisia-gui/9f98a6ce5f14ae1ae18295bb041000c370b43c35/irisia-book/src/banner.png -------------------------------------------------------------------------------- /irisia-book/src/build_syntax/case_structure.md: -------------------------------------------------------------------------------- 1 | # 选择结构 2 | 3 | 选择结构允许您根据条件选择表达式。它们会拓展为一个`enum`结构体,以储存不同类型的表达式。if和match表达式和rust的语法非常相似,除了表达式是元素声明语句。 4 | 5 | ## if表达式 6 | 7 | ```rust 8 | build! { 9 | if 1 + 1 == 2 { 10 | Element1 { 11 | props: "1 + 1 == 2 is true" 12 | } 13 | } 14 | 15 | if 1 + 2 == 3 { 16 | Element1 { 17 | props: "1 + 2 == 3 is true" 18 | } 19 | } else { 20 | Element2 { 21 | other_props: "oh no, something wrong happened!" 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | ## match表达式 28 | 29 | ```rust 30 | build! { 31 | match 20 { 32 | 1 => Element1 { 33 | props: "value is 1" 34 | }, 35 | 36 | num if num % 2 == 0 => Element2 { 37 | other_props: "value can be exact divided by 2" 38 | } 39 | 40 | _ => {} 41 | } 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /irisia-book/src/build_syntax/index.md: -------------------------------------------------------------------------------- 1 | # build语法 2 | 3 | `build`宏可帮助您快速构建一个节点树。 4 | 5 | ## `@init`指令 6 | 7 | `@init`是提供给`build`或`render_fn`宏基本参数的指令。一个宏最多只能有一个`@init`指令,而且*必须*出现在开头位置。 8 | 9 | - 在`build`宏中,`@init`指令是可选的,可接受0~2个参数。语法为`@init[([[, ]])];`。例如: 10 | - `@init;`:静态节点。 11 | - `@init(event_dispatcher);`:将监听元素创建事件。 12 | - `@init(event_dispathcer, slot);`:将监听元素创建事件,并且可使用一次`@slot`指令。 13 | - 在`render_fn`宏中,`@init`指令是必须的,只能接受1个参数。语法为`@init()`。例如: 14 | - `@init(self);`:等同于`fn render(&mut self, ..)..`。 15 | - `@init(foo);`:等同于`fn render(foo: &mut Self, ..)..`。 16 | 17 | ## 声明元素 18 | 19 | 声明一个元素分为以下几部分: 20 | 21 | ```text 22 | <元素名> { 23 | ( 24 | <属性名>: <表达式>, 25 | +<元素指令>: <表达式>, 26 | )* 27 | 28 | <子节点列表> 29 | } 30 | ``` 31 | 32 | 这是一个带有`text`和`select`属性的`TextBox`元素。 33 | 34 | ```rust 35 | TextBox { 36 | +id: ("text_box", 12345), 37 | text: "this is a &str", 38 | select: true, 39 | } 40 | ``` 41 | 42 | *元素内*指令和属性的不同之处在于它是以`+`开头的。目前,只有两个指令,`style`和`id`。其中,`style`指令可向该元素提供一个实现了`StyleContainer` trait的结构体,`id`则将表达式的值作为该元素的标识符,监听该元素的元素创建事件。关于这两个指令将在后面章节给出。 43 | 下面,来学习一下顺序结构、嵌套结构和拓展指令。 44 | -------------------------------------------------------------------------------- /irisia-book/src/build_syntax/repetitive_structure.md: -------------------------------------------------------------------------------- 1 | # 循环结构 2 | 3 | 循环结构可以让您生成多个相同类型的元素。但为了得到高效的缓存,循环结构中必须提供*键*。 4 | 5 | 键是用来确定每一个数据对应的元素缓存的。如果不使用键,那么一个迭代器中如果元素发生中间插入、中间删除、调换顺序,则会导致一连串元素的属性需要重新设置,进而可能导致大量元素需要改变其缓存,造成性能损耗。 6 | 7 | 下面展示了如果没有键,插入一个新数据的过程。 8 | 9 | ```text 10 | elements 1 2 3 5 6 7 11 | | | | | | | 12 | data 1 2 3 5 6 7 13 | ^ 14 | | 15 | 插入4 16 | ``` 17 | 18 | 所有数据对应的缓存将会后移一位,并在末尾创建一个新元素。 19 | 20 | ```text 21 | elements* 1 2 3 5 6 7 new 22 | | | | | | | | 23 | data 1 2 3 4 5 6 7 24 | ``` 25 | 26 | 再更新缓存。显而易见,这样的操作改变了这4个元素的缓存。 27 | 28 | ```text 29 | |------------| 30 | elements 1 2 3 | 4 5 6 7 | 31 | | | | | | | | | | 32 | data 1 2 3 | 4 5 6 7 | 33 | |------------| 34 | ``` 35 | 36 | 如果使用键,则第二步将变为: 37 | 38 | ```text 39 | 在此插入新元素 40 | | 41 | v 42 | elements* 1 2 3 5 6 7 43 | | | | \ \ \ 44 | data 1 2 3 4 5 6 7 45 | ^ 46 | | 47 | 插入4 48 | ``` 49 | 50 | ```text 51 | (new) 52 | elements 1 2 3 4 5 6 7 53 | | | | | | | | 54 | data 1 2 3 4 5 6 7 55 | ``` 56 | 57 | 这样,一次只需改变(初始化)一个元素的缓存就行了。 58 | 59 | ## for表达式 60 | 61 | for表达式中,`@key`指令是可选的。如果不指定`@key`,则默认使用迭代器元素作为键。这要求迭代器元素实现`Clone + Hash + Eq + 'static`,若不满足,则引起编译期错误。此时,需要您通过`@key`指令手动指定键。 62 | 63 | ```rust 64 | build! { 65 | for num in 0..10 { 66 | Element1 { 67 | props: num 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | 等同于 74 | 75 | ```rust 76 | build! { 77 | for num in 0..10 { 78 | @key num; 79 | Element1 { 80 | props: num 81 | } 82 | } 83 | } 84 | ``` 85 | 86 | 下面这个例子将会引发编译期错误 87 | 88 | ```rust 89 | let vec = vec![1, 2, 3]; 90 | build! { 91 | // 错误:num不满足`'static` 92 | for num in vec.iter() { 93 | Element1 { 94 | props: *num 95 | } 96 | } 97 | } 98 | ``` 99 | 100 | 解决办法是指定`@key`,或改为`iter().copied()`。 101 | 102 | ```rust 103 | let vec = vec![1, 2, 3]; 104 | build! { 105 | for num in vec.iter() { 106 | @key *num; 107 | Element1 { 108 | props: *num 109 | } 110 | } 111 | } 112 | ``` 113 | 114 | ```rust 115 | let vec = vec![1, 2, 3]; 116 | build! { 117 | for num in vec.iter().copied() { 118 | Element1 { 119 | props: *num 120 | } 121 | } 122 | } 123 | ``` 124 | 125 | ## while表达式 126 | 127 | 与for表达式不同的是,while表达式**必须指定**键。 128 | 129 | ```rust 130 | let mut iter = vec![1, 2, 3].into_iter(); 131 | build! { 132 | while let Some(num) = iter.next() { 133 | @key num; 134 | Element1 { 135 | props: num 136 | } 137 | } 138 | } 139 | ``` 140 | 141 | 如果您不知道指定什么键,请考虑下面的方案。这将回归到本章开始部分的“无键缓存方案”。 142 | 143 | ```rust 144 | build! { 145 | for _ in (0..).take_while(|_| expression_here) { 146 | Element1 { 147 | props: 0 148 | } 149 | } 150 | } 151 | ``` 152 | -------------------------------------------------------------------------------- /irisia-book/src/build_syntax/seq_nest_and_cmd.md: -------------------------------------------------------------------------------- 1 | # 顺序、嵌套和扩展 2 | 3 | 学习了如何声明一个元素,这篇章节将十分利于理解。 4 | 5 | ## 顺序结构 6 | 7 | 直接向下排列即可,*不能加逗号*。 8 | 9 | ```rust 10 | build! { 11 | Element1; 12 | Element2; 13 | 14 | Element3 { 15 | props: "value" 16 | } 17 | } 18 | ``` 19 | 20 | ## 嵌套结构 21 | 22 | 可以通过这种方式声明子元素。可添加独立的花括号来分割您的内容。 23 | 24 | ```rust 25 | build! { 26 | Div { 27 | Div { 28 | Element1; 29 | } 30 | 31 | { 32 | Element2; 33 | Element3 { 34 | props: "value" 35 | } 36 | } 37 | 38 | { 39 | Element4; 40 | Element5 41 | } 42 | } 43 | } 44 | ``` 45 | 46 | ## 拓展指令 47 | 48 | 可以通过`@extend <表达式>`语法来将其他元素树扩展到当前元素树。扩展的树*允许*多元素。 49 | 50 | ```rust 51 | let ext = build! { 52 | Element1; 53 | Element2; 54 | }; 55 | 56 | let branch = build! { 57 | @extend ext; 58 | Element3 { 59 | props: "value" 60 | } 61 | Element4; 62 | }; 63 | ``` 64 | 65 | ## 插槽指令 66 | 67 | 如果在`build`的`@init`指令中提供了插槽(Slot),或使用`render_fn`宏,则可以使用`@slot`指令来将插槽中的所有元素合并到当前树中。它和手动`@extend <插槽表达式>`的效果是一样的。 68 | 69 | ```rust 70 | render_fn! { 71 | Element1; 72 | Element2; 73 | @slot; 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /irisia-book/src/contributing.md: -------------------------------------------------------------------------------- 1 | # 征集开发者! 2 | 3 | ## 我可以做什么? 4 | 5 | ### 如果您没有使用GUI框架 6 | 7 | 那么您理解本项目可能需要花费一定时间,因为文档工作目前十分不完善。不过,如果您对GUI框架感兴趣,欢迎持续关注本项目,也可也向我(或我们)提问任何有关该项目的问题! 8 | 9 | ### 如果您使用过GUI框架 10 | 11 | 或许您是新手,或身经百战的老手,我们都希望您能够过目一下本项目examples目录下的`window.rs`示例,并试着了解一下我们的开发思路以及目前进度。请继续往下阅读,欢迎提出各种意见、改进措施、评价,您的任何意见对我们来说都十分宝贵! 12 | 13 | ## 我希望加入开发 14 | 15 | 首先感谢并欢迎您的加入! 16 | 17 | ### 现状 18 | 19 | 目前Irisia GUI尚处于早期阶段,优化和文档都不尽人意,也许还存在bug。这需要您理解Irisia GUI框架的设计理念,请向我提问任何您不理解的问题。 20 | 21 | 截至撰写这篇文档时,没有任何其他开发者。 22 | 23 | 以下几点是Irisia GUI目前未完工的部分: 24 | - GPU性能优化。目前测试运行窗口时GPU占用率过高,需要优化。 25 | - 平台移植。目前Irisia GUI是在Windows平台上开发和测试,理论上可应用于其他系统,但仍需进一步测试和兼容性改造。 26 | - 动画。目前Irisia GUI原生不支持动画,虽然是可以做出来的,但是这样写出来的代码风格不尽人意。 27 | - 预制类。目前Irisia GUI预制只有文本框。极度缺乏样式,控件和布局生成器。 28 | - 文档和翻译。目前文档大部分都是中文,而且没有注释。 29 | -------------------------------------------------------------------------------- /irisia-book/src/get_started/first_window.md: -------------------------------------------------------------------------------- 1 | # 第一个窗口 2 | 3 | ## 创建一个自定义元素 4 | 5 | 打开`main.rs`,修改为下面内容 6 | 7 | ```rust 8 | use irisia::Element; 9 | 10 | struct MyApp; 11 | impl Element for MyApp { 12 | // ... 13 | } 14 | ``` 15 | 16 | 现在,我们将要为`MyApp`实现`Element` trait。\ 17 | 首先,我们使用`irisia::render_fn`宏来为我们自动实现`render`函数。 18 | 19 | ```rust 20 | use irisia::{Element, render_fn}; 21 | 22 | #[derive(Default)] 23 | struct MyApp; 24 | 25 | impl Element for MyApp { 26 | render_fn! { 27 | @init(self); 28 | } 29 | } 30 | ``` 31 | 32 | `render_fn`宏的语法和`build`语法几乎完全相似,唯一区别就是`@init`函数。可以在这里找到[`build`宏](../build_syntax/index.html)的详细用法。由于rust十分注重宏的[卫生性](https://veykril.github.io/tlborm/decl-macros/minutiae/hygiene.html),所以用户必须手动提供`self`关键字*使self可访问*。当然,您也可以使用任意有效标识符,例如`@init(foo)`,它们会编译为`fn render(foo: &mut Self, ..) ..`。 33 | 然后,我们添加一个文本框。 34 | 35 | ```rust 36 | use irisia::{ 37 | textbox::TextBox, 38 | Element, 39 | render_fn, 40 | element::{NeverInitalized, NoProps}, 41 | }; 42 | 43 | #[derive(Default)] 44 | struct MyApp; 45 | 46 | impl Element for MyApp { 47 | render_fn! { 48 | @init(self); 49 | TextBox { 50 | text: "hello world" 51 | } 52 | } 53 | 54 | fn create(_: irisia::element::RuntimeInit) -> Self { 55 | Self{} 56 | } 57 | 58 | type Props<'a> = NoProps; 59 | type ChildProps<'a> = NeverInitalized; 60 | } 61 | ``` 62 | 63 | 这样,我们的第一个元素就设计完成了。 64 | 65 | ## 添加main函数 66 | 67 | 我们现在需要创建一个窗口让它运行起来。我们添加一个程序入口, 68 | 让它启动一个窗口来将我们的元素作为根元素渲染。 69 | 70 | ```rust 71 | use irisia::{ 72 | textbox::TextBox, 73 | Element, 74 | Window, 75 | render_fn, 76 | }; 77 | 78 | # #[derive(Default)] 79 | # struct MyApp; 80 | # 81 | # impl Element for MyApp { 82 | # render_fn! { 83 | # @init(self); 84 | # TextBox { 85 | # text: "hello world" 86 | # } 87 | # } 88 | # } 89 | # 90 | #[irisia::main] 91 | async fn main() { 92 | Window::new::("my first app").await.unwrap().join().await; 93 | } 94 | ``` 95 | 96 | 点击运行即可看到一个标题为`my first app`的窗口中渲染的`hello world`字样了。 97 | 98 | 您可能会疑惑,`MyApp`是一个零长度的结构体,那么有缓存吗?是的,这也是有缓存的。`render_fn!`会帮我们接收一个`&mut CacheBox`参数,我们定义的`TextBox`,以及后续您添加的其他元素都会一并缓存在这个缓存盒里,由一个名为`AddChildCache`的结构体代理。为了让用户专注于应用设计,我们没有让用户接管这部分。但是请注意,如果您选择手动实现`render`方法,**请不要用CacheBox缓存不同类型的元素**,这样会导致debug模式下程序panic,release模式下大量性能开销和元素创建。 99 | 100 | 总而言之,如果您使用`render_fn`宏,请放心,irisia能够保证妥善保管元素的缓存。 101 | -------------------------------------------------------------------------------- /irisia-book/src/get_started/installation.md: -------------------------------------------------------------------------------- 1 | # 安装环境 2 | 3 | 接下来,请选择一个您想要编译到的平台 4 | 5 | - [Windows](on_windows.html) 6 | - [Android]() 7 | - [Linux]() -------------------------------------------------------------------------------- /irisia-book/src/get_started/on_windows.md: -------------------------------------------------------------------------------- 1 | # Windows环境 2 | 3 | ## 安装Rust 4 | 5 | 请跟随[官网指引](https://www.rust-lang.org/tools/install),安装Rust。 6 | 7 | ## 将irisia引入您的项目 8 | 9 | 创建一个新项目,并进入该目录 10 | 11 | ```shell 12 | > cargo new my-first-app 13 | > cd my-first-app 14 | ``` 15 | 16 | 打开`Cargo.toml`文件,并在项目的`[dependencies]`下添加依赖。由于该库尚未正式发布,因此仅能通过git链接进行安装。 17 | 18 | ```toml 19 | irisia = { git = "https://github.com/Fancyflame/irisia-gui.git" } 20 | ``` 21 | 22 | 然后等待安装完成。 23 | 24 | ## 如果安装过程中`skia-safe`编译错误 25 | 26 | 有时因为各种原因,`skia-safe`无法正常编译。在本文中,推荐使用预编译包,这样您无需安装skia编译环境并等待编译。如无法下载预编译包,请检查网络连接是否正常。若仍无法下载,请采取手动下载方式,如下文。 27 | 28 | 1. 进入[`skia-binaries`](https://github.com/rust-skia/skia-binaries/releases)发布页 29 | 2. 下载最新预编译包,格式为`skia-binaries-<编号>-<架构>-pc-windows-msvc-textlayout.tar.gz`,如`skia-binaries-4f106aa048fa92fce6ce-x86_64-pc-windows-msvc-textlayout.tar.gz` 30 | 3. 将压缩包复制到合适的文件夹,并选择下列任意一种方法 31 | - (推荐)在系统搜索栏中搜索`PATH`,选择编辑系统环境变量,新建环境变量`SKIA_BINARIES_URL`,值为`file://<目录>\skia-binaries-{key}.tar.gz`,例如`file://C:\path\to\directory\skia-binaries-{key}.tar.gz`。注意,`{key}`要保留源文本 32 | - 启动文件服务器[^1],将上面步骤的值改为`<服务器地址>/skia-binaries-{key}.tar.gz`,例如`http://127.0.0.1:8000/skia-binaries-{key}.tar.gz` 33 | 4. 重启您的控制台,或设置同样的环境变量,使得您的控制台能够识别您新设置的`SKIA_BINARIES_URL`环境变量 34 | 5. 重新编译一次 35 | 36 | ## 如果出现skia链接错误 37 | 38 | 原issue [rust-skia#660](https://github.com/rust-skia/rust-skia/issues/660) 39 | 主要原因是构建工具MSBuild版本过低,比如您使用的是MSBuild 2019。您可以参考以下更新方案: 40 | 1. 访问获得MSBuild安装程序 41 | 2. 启动安装程序,安装最新Visual Studio构建工具 42 | 3. 点击“修改”,打开“单个组件” 43 | 4. 勾选最新MSVC生成工具,例如`MSVC v143 - VS 2022 C++ x64/x86 生成工具(最新)` 44 | 5. 勾选最新Windows SDK,例如`Windows 11 SDK (10.0.22621.0)` 45 | 6. 点击“修改”,等待安装完成 46 | 7. 重新编译一次 47 | 48 | [^1]: 推荐选用nodejs或python文件服务器。如果您选用的是nodejs服务器,输入`http-server C:\path\to\directory -p 8000`来启动(需安装[`http-server`](https://www.npmjs.com/package/http-server))。如果您选用的是python服务器,输入`python -m http.server -d C:\path\to\directory -p 8000`来启动。 49 | -------------------------------------------------------------------------------- /irisia-book/src/index.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | Book of Irisia GUI是包含了Irisia GUI框架的学习使用、深入了解和参与开发的文档合集。 4 | -------------------------------------------------------------------------------- /irisia-book/src/irisia_and_me.md: -------------------------------------------------------------------------------- 1 | # 我和Irisia的故事 2 | 3 | 这里只是叙事文,没有什么资料在这里,可以跳过。 4 | 5 | ## Irisia GUI的诞生 6 | 7 | 撰写这篇文档时是2023年4月份,我是一名大二本科生,8年前开始自学编程,2021年接触Rust。由于我很早开始接触电子游戏Minecraft(我的世界),因此受到其影响,走上编程道路。在一开始,我通过编写JSON文件使游戏中的生物做出各种动作开始,慢慢接触了JS,接着进入Web的领域,利用手边的各种框架(有Vue开发经验)和JS库开发与Minecraft相关工具,包括皮肤制作器ShadowSkin和联机工具MCMU,这些都可以在[我的GitHub个人主页](https://github.com/Fancyflame)找到。经过几年和Web端开发,我逐渐理解了GUI的设计理念和网络编程的方式。这些经验为我开发Irisia GUI打下了设计方向的基础。 8 | 9 | 时间来到2021年的春天,经朋友介绍认识了Rust这门语言。当我开始试着了解它时,就和它的编程理念一拍即合,于是开始了Rust的学习生涯。由于我是从Web端“转行”到Rust,因此花费了半个多月才具备设计Rust程序的能力。之后经过长达一年的磨合,我通过Rust异步编程开发了MCMU的Rust版本——MCMU2,后来迭代出改进版的MCMU2-alpha版本。借助Rust的力量,MCMU的性能和功能都逐渐增加,但是也面临着一个重要的问题——没有图形界面。 10 | 11 | 起初我并没有打算自己做GUI,而是放眼crates.io去寻找合适的GUI框架,但却收获寥寥。我尝试了一些比较热门的GUI框架,例如Iced,Druid,imgui甚至tauri,但都不能令我满意。在经过长时间考察后,我决定亲手设计一个GUI框架,这就是Irisia GUI。 12 | 13 | ## 准备工作 14 | 15 | 在开始着手设计Irisia GUI前,刚开始我必须选择一个图形库。因为当时Rust生态中的图形库功能参差不齐,本身也尚未成熟,甚至连阴影盒子的功能都没有得到良好的支持,所以我没有局限在纯Rust开发的图形库中。我开始寻找FFI方案,很快发现Skia具有良好的跨平台性、强大的功能以及方便易用的Rust绑定。我设计了一些简单的测试程序,在由winit启动的窗口上,测试Skia在Windows平台和Android平台的绘制正常后,定下来使用Skia作为图形库后端。 16 | 17 | 紧接着我需要解决窗口启动器的问题。虽然我认可winit的跨平台性,但是它仍缺失一些关键功能,例如在Android端弹出虚拟键盘和启用剪贴板功能。于是我在winit的repo中提了相关issue,得到了基于jni弹出虚拟键盘和利用copypasta库获得剪贴板功能的解决方案。前者运行良好,但是后者却缺少移动端支持。于是我模仿基于jni弹出虚拟键盘的方案,很快实现了对安卓剪贴板操控方式的支持方案,并提交了Pull Request。项目管理者十分相中这个实现方式,但由于仓库代码的格式化和rustfmt默认格式不同,最终没有成功Merge到主分支,后续我也没有再关注。但可以确定的是,这两个方案运行良好,解决了刚需问题。 18 | 19 | 在我试着兼容winit的同时,并没有放弃尝试别的窗口启动器。我尝试了sdl2的方案,发现它的功能和winit近似。在测试跨平台性时,Windows端不需要过多干预即可运行,而Android端就没有那么幸运了。为了能在Android端编译运行,我学习了如何使用Gradle,利用插件对Rust工程打包,但是过程比较繁琐,这也是后来没有选择sdl2的原因。最终,sdl2带动skia在Windows端和Android端成功运行。但是,又因为rust绑定的sdl2的文档比较缺乏,维护不足,因此最终放弃了该方案。 -------------------------------------------------------------------------------- /irisia-book/src/principle.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fancyflame/irisia-gui/9f98a6ce5f14ae1ae18295bb041000c370b43c35/irisia-book/src/principle.ai -------------------------------------------------------------------------------- /irisia-book/src/principle.md: -------------------------------------------------------------------------------- 1 | # 设计原理 2 | 3 | 学习Irisia GUI的设计理念有助于您理解Irisia GUI。 4 | 5 | ## main函数 6 | 7 | 下图展示了一个Irisia程序的入口函数工作例程。 8 | ```text 9 | 程序开始 -> 启动运行时 -> 创建窗口 ------ ----> 退出 10 | ^ | 11 | |--------- 12 | ``` 13 | 很简单,对吧? 14 | 15 | main函数要做的工作就是创建一个或多个窗口,然后等待窗口关闭,最后退出。在这里没有什么要点。 16 | 17 | ## 渲染周期 18 | 19 | 当一个窗口初始化完成后,需要提供一个根元素,然后进入第一个渲染周期,循环。 20 | 一个窗口会接收3种事件: 21 | 1. 更新树事件:以30次每秒速度触发。当接收到该事件后,会重构结点树,检查缓存。如果元素更新缓存,则向窗口请求重绘。 22 | 2. 重绘事件:最高以120fps更新,取决于计算机性能。当更新节点树事件检查到更改后,会请求重绘。同时含动画元素也可以申请动画帧(animation frame),而独立于更新树事件,达到更流畅的动画效果。 23 | 3. 窗口事件:鼠标事件、键盘事件等。它可以以高于120fps的速度刷新,取决于当前电子设备的硬件。 24 | 25 | ![principle picture](principle.png) -------------------------------------------------------------------------------- /irisia-book/src/principle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fancyflame/irisia-gui/9f98a6ce5f14ae1ae18295bb041000c370b43c35/irisia-book/src/principle.png -------------------------------------------------------------------------------- /irisia-book/src/style_syntax/animation.md: -------------------------------------------------------------------------------- 1 | # 动画 2 | 3 | 正在开发中... 4 | -------------------------------------------------------------------------------- /irisia-book/src/style_syntax/index.md: -------------------------------------------------------------------------------- 1 | # style语法 2 | 3 | ## 声明样式 4 | 5 | 声明样式的语法为 6 | 7 | ```text 8 | <样式类型>: [<参数>, ..][.<选项> [<值>], ..]; 9 | ``` 10 | 11 |
12 | 13 | 为了方便起见,当一个样式在宏内是以snake_case方式[^1]被声明时,将被解析为CamelCase,然后在头部添加“Style”。例如“width”将被转义为“StyleWidth”。 14 | 15 |
16 | 17 | [^1]: snake_case是一种命名规则,用于将多个单词连接成一个单词并用下划线分隔。在snake_case中,单词以小写字母表示,并使用下划线分隔,例如:my_variable_name。而在rust中,类型应该以CamelCase的命名方式出现。 18 | -------------------------------------------------------------------------------- /irisia-book/src/why_irisia_gui.md: -------------------------------------------------------------------------------- 1 | # 为什么选择irisia? 2 | 3 | ## 高性能和高表现力 4 | 5 | irisia采用winit作为窗口启动器,是rust最热门的窗口启动器之一。 6 | 7 | irisia采用skia作为渲染后端。skia是由谷歌公司开发的跨平台图形库,应用于Chrome浏览器和Android原生渲染。经过长时间的考验,其稳定性和性能都十分出类拔萃。除此之外,skia拥有非常完善的字体渲染模块和绘制功能,使得irisia能在您的屏幕上呈现更多精彩画面。 8 | 9 | ## 跨平台 10 | 11 | 得益于`winit`和`skia-safe`良好的跨平台性,irisia可以跨桌面端和移动端。对Windows、Linux和Android平台的支持是我们的工作重心。 12 | 13 | ## 缓存 14 | 15 | 如果您阅读了[前一章节](index.html),您或许会注意到各种语法结构。请不用担心,我们将会对任何语法结构——顺序、选择和循环结构中所有元素进行缓存,使每一帧都以最小开销渲染。 16 | 17 | 或许您之前使用过React框架,与之不同的是irisia的循环语句可以储存任意可以被[HashMap](https://doc.rust-lang.org/std/collections/struct.HashMap.html#)作为键的结构体,但是强制您提供一个键。尽管这在`for`语句中,`@key`指令并不是必须的,那是因为在未指定`@key`时,默认使用迭代器中的元素作为键。 18 | 19 | ## 异步系统 20 | 21 | irisia内部分为两种工作模式:同步代码和异步代码。 22 | 23 | 如果您不是一个组件开发者,您将很小概率和同步代码部分打交道。同步代码大多作用在渲染部分,它要求不能等待,必须高效渲染每一帧。 24 | 25 | 相比之下,异步代码几乎全部作用在事件处理部分,您可以将等待事件和其他如文件系统和网络等异步IO更好地协作,这样能很好地提升工作效率,并避免回调地狱(Callback hell)。 26 | 27 | ## 高度自定义 28 | 29 | irisia允许您最大限度地自定义您的元素,您可以自定义元素布局、组件和样式。用户是可以还原出标准库中的文本框`TextBox`的。 30 | 31 | 在具有高自定义度的同时,我们也同样重视开发效率。如果您只想利用现成的模块快速构建您的应用程序,请多多熟悉`build`和`style`(以及`render_fn`)宏吧! 32 | 33 | ## 丰富的宏 34 | 35 | irisia很大程度上利用了rust的元编程系统。如前面您所见的整个库的核心宏`build`和`style`。除此之外,也有专门用于自定义样式的派生宏`Style`等。 36 | 37 | ```rust 38 | use irisia::{Style, Pixel, skia_safe::Color}; 39 | 40 | #[derive(Style)] 41 | #[irisia( 42 | from = "x_offset, y_offset[, blur_radius][, spread_radius][, color]", 43 | impl_default 44 | )] 45 | struct StyleBoxShadow { 46 | #[irisia(default)] 47 | x_offset: Pixel, 48 | 49 | #[irisia(default)] 50 | y_offset: Pixel, 51 | 52 | #[irisia(default)] 53 | blur_radius: Pixel, 54 | 55 | #[irisia(default)] 56 | spread_radius: Pixel, 57 | 58 | #[irisia(default = "Color::BLACK")] 59 | color: Color 60 | } 61 | 62 | fn main() { 63 | let style = style! { 64 | box_shadow: 0px, 0px; 65 | box_shadow: 0px, 0px, 10px; 66 | // ... 67 | }; 68 | } 69 | ``` 70 | -------------------------------------------------------------------------------- /irisia-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "irisia_macros" 3 | version = "1.0.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | syn = { version = ">=2.0.38", features = [ 13 | "full", 14 | "extra-traits", 15 | "visit", 16 | "visit-mut", 17 | ] } 18 | quote = "1.0" 19 | proc-macro2 = "1.0" 20 | case = "1.0" 21 | 22 | [features] 23 | default = ["macro-dep-guessing"] 24 | macro-dep-guessing = [] 25 | -------------------------------------------------------------------------------- /irisia-macros/src/derive_props/attrs/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use proc_macro2::Ident; 4 | use syn::{Expr, ExprPath, Type, Visibility}; 5 | 6 | mod parse_field_attr; 7 | mod parse_struct_attr; 8 | 9 | pub struct StructAttr { 10 | pub updater_name: Ident, 11 | pub visibility: Visibility, 12 | pub update_result: Ident, 13 | pub default_watch: Option, 14 | } 15 | 16 | pub struct DefaultWatch { 17 | pub group_name: Ident, 18 | pub exclude: HashSet, 19 | } 20 | 21 | pub struct FieldAttr { 22 | pub value_resolver: FieldResolver, 23 | pub default_behavior: FieldDefault, 24 | pub rename: Option, 25 | pub watch: Option, 26 | } 27 | 28 | #[derive(Clone)] 29 | pub enum FieldResolver { 30 | MoveOwnership, 31 | CallUpdater, 32 | ReadStyle { as_std_input: bool }, 33 | WithFn { path: ExprPath, arg_type: Type }, 34 | Custom(Type), 35 | } 36 | 37 | #[derive(Clone)] 38 | pub enum FieldDefault { 39 | MustInit, 40 | Default, 41 | DefaultWith(Expr), 42 | } 43 | 44 | impl FieldAttr { 45 | pub fn is_std_style_input(&self) -> bool { 46 | matches!( 47 | self.value_resolver, 48 | FieldResolver::ReadStyle { as_std_input: true } 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /irisia-macros/src/derive_props/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use case::CaseExt; 4 | use proc_macro2::TokenStream; 5 | use quote::format_ident; 6 | use syn::{ 7 | parse_quote, punctuated::Punctuated, spanned::Spanned, visit::Visit, Error, Fields, 8 | GenericParam, Generics, Ident, ItemStruct, Result, Type, 9 | }; 10 | 11 | use crate::derive_props::attrs::StructAttr; 12 | 13 | use self::{ 14 | attrs::FieldAttr, impl_miscellaneous::impl_miscellaneous, impl_update_with::impl_update_with, 15 | }; 16 | 17 | mod attrs; 18 | mod impl_miscellaneous; 19 | mod impl_update_with; 20 | 21 | #[non_exhaustive] 22 | struct GenHelper<'a> { 23 | item: &'a ItemStruct, 24 | struct_attr: StructAttr, 25 | updater_generics: Generics, 26 | fields: Vec>, 27 | } 28 | 29 | struct HandledField<'a> { 30 | ident: &'a Ident, 31 | ty: &'a Type, 32 | attr: FieldAttr, 33 | } 34 | 35 | impl<'a> GenHelper<'a> { 36 | fn new(item: &'a ItemStruct, struct_attr: StructAttr, fields: Vec>) -> Self { 37 | Self { 38 | item, 39 | updater_generics: new_generics(item), 40 | fields, 41 | struct_attr, 42 | } 43 | } 44 | 45 | fn generics_iter(&self) -> impl Iterator + Clone { 46 | self.updater_generics 47 | .params 48 | .iter() 49 | .map(|param| match param { 50 | GenericParam::Type(t) => &t.ident, 51 | _ => unreachable!( 52 | "any `GenericParam` other than `GenericParam::Type` is not allowed" 53 | ), 54 | }) 55 | } 56 | } 57 | 58 | fn new_generics(stru: &ItemStruct) -> Generics { 59 | let field_types: HashSet<&Ident> = { 60 | struct IdentVisitor<'ast>(HashSet<&'ast Ident>); 61 | impl<'ast> Visit<'ast> for IdentVisitor<'ast> { 62 | fn visit_ident(&mut self, i: &'ast Ident) { 63 | self.0.insert(i); 64 | } 65 | } 66 | 67 | let mut ident_visitor = IdentVisitor(HashSet::new()); 68 | syn::visit::visit_item_struct(&mut ident_visitor, stru); 69 | ident_visitor.0 70 | }; 71 | 72 | let param_iter = stru.fields.iter().map(|field| { 73 | let raw_id = field 74 | .ident 75 | .as_ref() 76 | .expect("expected named field") 77 | .to_string() 78 | .to_camel(); 79 | 80 | let mut id = format_ident!("Prop{raw_id}"); 81 | loop { 82 | if !field_types.contains(&id) { 83 | let gp: GenericParam = parse_quote!(#id); 84 | break gp; 85 | } 86 | id = format_ident!("{id}Generic"); 87 | } 88 | }); 89 | 90 | Generics { 91 | params: Punctuated::from_iter(param_iter), 92 | ..Default::default() 93 | } 94 | } 95 | 96 | pub fn props(attr: TokenStream, item: ItemStruct) -> Result { 97 | if !matches!(item.fields, Fields::Named(_)) { 98 | return Err(Error::new( 99 | item.span(), 100 | "expected a struct with named fields", 101 | )); 102 | } 103 | 104 | let field_attrs: Vec = { 105 | let mut attrs = Vec::new(); 106 | for field in item.fields.iter() { 107 | let ident = field.ident.as_ref().unwrap(); 108 | attrs.push(HandledField { 109 | ident, 110 | ty: &field.ty, 111 | attr: FieldAttr::parse_from(&field.attrs, ident)?, 112 | }); 113 | } 114 | attrs 115 | }; 116 | 117 | let struct_attr = StructAttr::parse_from(attr, &field_attrs)?; 118 | 119 | let helper = GenHelper::new(&item, struct_attr, field_attrs); 120 | 121 | let mut output = impl_miscellaneous(&helper); 122 | output.extend(impl_update_with(&helper)); 123 | 124 | Ok(output) 125 | } 126 | -------------------------------------------------------------------------------- /irisia-macros/src/derive_style/attributes/mod.rs: -------------------------------------------------------------------------------- 1 | use syn::{meta::ParseNestedMeta, token::Paren, Attribute, Expr, Ident, LitStr, Result, Token}; 2 | 3 | pub use parse_paths::Segment; 4 | 5 | mod parse_paths; 6 | 7 | #[derive(Debug)] 8 | pub enum DeriveAttr { 9 | Skip, 10 | From { 11 | instruction: Option>>, 12 | }, 13 | Default { 14 | specified: Option, 15 | }, 16 | Option { 17 | rename: Option, 18 | set_true: bool, 19 | }, 20 | ImplDefault, 21 | } 22 | 23 | impl DeriveAttr { 24 | pub fn parse_attr(attr: &Attribute) -> Result> { 25 | if !attr.path().is_ident("style") { 26 | return Ok(vec![]); 27 | } 28 | 29 | let mut attrs = Vec::new(); 30 | attr.parse_nested_meta(|meta2| { 31 | attrs.push(parse_logic(meta2)?); 32 | Ok(()) 33 | })?; 34 | 35 | Ok(attrs) 36 | } 37 | } 38 | 39 | pub fn get_attrs(attrs: &[Attribute]) -> Result> { 40 | let mut output = Vec::new(); 41 | for attr in attrs { 42 | output.extend(DeriveAttr::parse_attr(attr)?); 43 | } 44 | Ok(output) 45 | } 46 | 47 | fn parse_logic(meta: ParseNestedMeta) -> Result { 48 | let name = match meta.path.get_ident() { 49 | Some(name) => name.to_string(), 50 | None => return Err(meta.error("expected path")), 51 | }; 52 | 53 | match &*name { 54 | "impl_default" => Ok(DeriveAttr::ImplDefault), 55 | "skip" => Ok(DeriveAttr::Skip), 56 | "from" => parse_from(meta), 57 | "default" => parse_default(meta), 58 | "option" => parse_option(meta), 59 | other => Err(meta.error(format!("unknown attribute `{other}`"))), 60 | } 61 | } 62 | 63 | fn parse_from(input: ParseNestedMeta) -> Result { 64 | Ok(DeriveAttr::From { 65 | instruction: if input.input.peek(Token![=]) { 66 | Some(parse_paths::parse_paths(input.value()?)?) 67 | } else { 68 | None 69 | }, 70 | }) 71 | } 72 | 73 | fn parse_default(input: ParseNestedMeta) -> Result { 74 | if input.input.peek(Token![=]) { 75 | let litstr: LitStr = input.value()?.parse()?; 76 | syn::parse_str(&litstr.value()).map(|expr| DeriveAttr::Default { 77 | specified: Some(expr), 78 | }) 79 | } else { 80 | Ok(DeriveAttr::Default { specified: None }) 81 | } 82 | } 83 | 84 | fn parse_option(meta: ParseNestedMeta) -> Result { 85 | let mut rename = None; 86 | let mut set_true = false; 87 | 88 | let mut exec_rename = |m: ParseNestedMeta| -> Result<()> { 89 | let value = m.value()?.parse::()?.value(); 90 | rename = Some(syn::parse_str::(&value)?); 91 | Ok(()) 92 | }; 93 | 94 | if meta.input.peek(Token![=]) { 95 | exec_rename(meta)?; 96 | } else if meta.input.peek(Paren) { 97 | meta.parse_nested_meta(|meta2| { 98 | let path2 = &meta2.path; 99 | if path2.is_ident("set_true") { 100 | set_true = true; 101 | Ok(()) 102 | } else if path2.is_ident("rename") { 103 | exec_rename(meta2)?; 104 | Ok(()) 105 | } else { 106 | match path2.get_ident() { 107 | Some(i) => Err(meta2.error(format!("unknown method `{i}`"))), 108 | None => Err(meta2.error("expected method name")), 109 | } 110 | } 111 | })?; 112 | } 113 | 114 | Ok(DeriveAttr::Option { rename, set_true }) 115 | } 116 | 117 | impl DeriveAttr { 118 | pub const fn attr_name(&self) -> &'static str { 119 | match self { 120 | DeriveAttr::Default { .. } => "default", 121 | DeriveAttr::From { .. } => "from", 122 | DeriveAttr::ImplDefault => "impl_default", 123 | DeriveAttr::Option { .. } => "option", 124 | DeriveAttr::Skip => "skip", 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /irisia-macros/src/derive_style/codegen_utils/append_path.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{punctuated::Punctuated, Index, Member, Token, Type, TypeTuple}; 4 | 5 | use crate::derive_style::variant_analyzer::{ 6 | full_quality_paths::FullQualitySegment, VariantAnalysis, 7 | }; 8 | 9 | use super::CodeGenerator; 10 | 11 | impl CodeGenerator { 12 | pub fn append_path(&mut self, va: &VariantAnalysis) { 13 | let variant = self.variant.clone(); 14 | 15 | for path in va.paths.iter() { 16 | let (ty, body) = impl_one_path(path); 17 | let body = va.field_type.surround(body.into_iter()); 18 | 19 | self.impl_trait( 20 | quote!(std::convert::From<#ty>), 21 | quote! { 22 | fn from(value: #ty) -> Self { 23 | #variant #body 24 | } 25 | }, 26 | ) 27 | } 28 | } 29 | } 30 | 31 | fn impl_one_path( 32 | path: &[(Member, FullQualitySegment)], 33 | ) -> (TypeTuple, Vec<(&Member, TokenStream)>) { 34 | let mut elems: Punctuated = Punctuated::new(); 35 | let mut output = Vec::new(); 36 | 37 | for (tag, seg) in path { 38 | let tuple_index = elems.len(); 39 | let value = match seg { 40 | FullQualitySegment::Required(ty) => { 41 | elems.push(ty.clone()); 42 | let tuple_index: Index = tuple_index.into(); 43 | quote!(value.#tuple_index) 44 | } 45 | FullQualitySegment::Fn { fn_path, arg_types } => { 46 | elems.extend(arg_types.iter().cloned()); 47 | let index_iter = (tuple_index..tuple_index + arg_types.len()).map(Index::from); 48 | quote!(#fn_path(#(value.#index_iter),*)) 49 | } 50 | FullQualitySegment::Default(def) => def.to_token_stream(), 51 | }; 52 | 53 | output.push((tag, value)); 54 | } 55 | 56 | ( 57 | TypeTuple { 58 | elems, 59 | paren_token: Default::default(), 60 | }, 61 | output, 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /irisia-macros/src/derive_style/codegen_utils/impl_default.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{Error, Result}; 3 | 4 | use crate::derive_style::{tag_to_string, variant_analyzer::VariantAnalysis}; 5 | 6 | use super::CodeGenerator; 7 | 8 | impl CodeGenerator { 9 | pub fn impl_default(&mut self, va: &VariantAnalysis) -> Result<()> { 10 | let mut body = Vec::new(); 11 | 12 | for (tag, field) in va.fields.iter() { 13 | match &field.default { 14 | Some(def) => body.push((tag, def)), 15 | None => { 16 | return Err(Error::new_spanned( 17 | tag, 18 | format!( 19 | "cannot implement `Default` for `{}`, because field `{}` does not have default behavior", 20 | self.ident, tag_to_string(tag) 21 | ) 22 | )) 23 | } 24 | } 25 | } 26 | let body = va.field_type.surround(body.into_iter()); 27 | 28 | let variant = self.variant.clone(); 29 | self.impl_trait( 30 | quote!(::std::default::Default), 31 | quote! { 32 | fn default() -> Self { 33 | #variant #body 34 | } 35 | }, 36 | ); 37 | Ok(()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /irisia-macros/src/derive_style/codegen_utils/mod.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use proc_macro2::{Ident, TokenStream}; 4 | use quote::{quote, ToTokens}; 5 | use syn::Generics; 6 | 7 | mod append_path; 8 | mod impl_default; 9 | 10 | #[derive(Clone)] 11 | pub struct CodeGenerator { 12 | tokens: TokenStream, 13 | ident: Ident, 14 | variant: Rc, 15 | generics: Generics, 16 | } 17 | 18 | impl CodeGenerator { 19 | pub fn new(ident: Ident, variant: Option<&Ident>, generics: Generics) -> Self { 20 | Self { 21 | tokens: TokenStream::new(), 22 | ident, 23 | variant: Rc::new(match variant { 24 | Some(v) => quote!(Self::#v), 25 | None => quote!(Self), 26 | }), 27 | generics, 28 | } 29 | } 30 | 31 | pub fn impl_style(&mut self) { 32 | self.impl_trait(quote!(irisia::Style), quote!()); 33 | } 34 | 35 | pub fn impl_trait(&mut self, trait_tokens: T, body: U) 36 | where 37 | T: ToTokens, 38 | U: ToTokens, 39 | { 40 | let (impl_gen, type_gen, where_clause) = self.generics.split_for_impl(); 41 | let ident = &self.ident; 42 | quote! { 43 | #[automatically_derived] 44 | impl #impl_gen #trait_tokens for #ident #type_gen 45 | #where_clause 46 | { 47 | #body 48 | } 49 | } 50 | .to_tokens(&mut self.tokens); 51 | } 52 | 53 | pub fn append_fn(&mut self, body: T) 54 | where 55 | T: ToTokens, 56 | { 57 | let (impl_gen, type_gen, where_clause) = self.generics.split_for_impl(); 58 | let ident = &self.ident; 59 | quote! { 60 | impl #impl_gen #ident #type_gen 61 | #where_clause 62 | { 63 | #body 64 | } 65 | } 66 | .to_tokens(&mut self.tokens); 67 | } 68 | 69 | pub fn finish(self) -> TokenStream { 70 | self.tokens 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /irisia-macros/src/derive_style/derive_struct.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use quote::quote; 3 | use syn::{Data, DataStruct, DeriveInput, Error, Result}; 4 | 5 | use super::{ 6 | codegen_utils::CodeGenerator, 7 | variant_analyzer::{read_fields::FieldAnalysis, VariantAnalysis}, 8 | }; 9 | 10 | pub fn derive_style_for_struct(input: DeriveInput) -> Result { 11 | let mut codegen = CodeGenerator::new(input.ident.clone(), None, input.generics); 12 | 13 | let analysis = match input.data { 14 | Data::Struct(DataStruct { fields, .. }) => { 15 | VariantAnalysis::analyze_fields(&input.attrs, &fields)? 16 | } 17 | _ => unreachable!("expected `DataStruct`, found {:?}", input.data), 18 | }; 19 | 20 | if analysis.option.is_some() { 21 | return Err(Error::new( 22 | Span::call_site(), 23 | "`option` attribute is not allowed in item struct", 24 | )); 25 | } 26 | 27 | codegen.impl_style(); 28 | codegen.append_path(&analysis); 29 | for field in analysis.fields.values() { 30 | try_append_option(&mut codegen, field); 31 | } 32 | 33 | if analysis.impl_default { 34 | codegen.impl_default(&analysis)?; 35 | } 36 | 37 | Ok(codegen.finish()) 38 | } 39 | 40 | fn try_append_option(codegen: &mut CodeGenerator, field: &FieldAnalysis) { 41 | let FieldAnalysis { 42 | tag, 43 | ty, 44 | option, 45 | option_set_true, 46 | .. 47 | } = field; 48 | 49 | let Some(fn_name) = option 50 | else { 51 | return; 52 | }; 53 | 54 | let body = if *option_set_true { 55 | quote! { 56 | pub fn #fn_name(&mut self) { 57 | self.#tag = true; 58 | } 59 | } 60 | } else { 61 | quote! { 62 | pub fn #fn_name(&mut self, value: #ty) { 63 | self.#tag = value; 64 | } 65 | } 66 | }; 67 | 68 | codegen.append_fn(body); 69 | } 70 | -------------------------------------------------------------------------------- /irisia-macros/src/derive_style/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use syn::{Data, DeriveInput, Error, Member, Result}; 3 | 4 | mod attributes; 5 | mod codegen_utils; 6 | mod derive_enum; 7 | mod derive_struct; 8 | mod variant_analyzer; 9 | 10 | pub fn derive_style(derive: DeriveInput) -> Result { 11 | match &derive.data { 12 | Data::Struct(_) => derive_struct::derive_style_for_struct(derive), 13 | Data::Enum(_) => derive_enum::derive_style_for_enum(derive), 14 | Data::Union(_) => Err(Error::new(Span::call_site(), "union is unsupported")), 15 | } 16 | } 17 | 18 | pub fn tag_to_string(tag: &Member) -> String { 19 | match tag { 20 | Member::Named(id) => id.to_string(), 21 | Member::Unnamed(index) => index.index.to_string(), 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /irisia-macros/src/derive_style/variant_analyzer/full_quality_paths.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use syn::{ 3 | punctuated::Punctuated, spanned::Spanned, Error, Expr, ExprPath, Member, Result, Token, Type, 4 | }; 5 | 6 | use crate::derive_style::attributes::Segment; 7 | use crate::derive_style::tag_to_string; 8 | 9 | use super::FieldMap; 10 | 11 | type SegmentVec = Vec<(Member, FullQualitySegment)>; 12 | 13 | #[derive(Debug)] 14 | pub struct FullQualityPaths { 15 | field_count: usize, 16 | segs: SegmentVec, 17 | } 18 | 19 | #[derive(Debug)] 20 | pub enum FullQualitySegment { 21 | Required(Type), 22 | Fn { 23 | fn_path: ExprPath, 24 | arg_types: Punctuated, 25 | }, 26 | Default(Expr), 27 | } 28 | 29 | impl FullQualityPaths { 30 | pub(super) fn complete(origin_paths: Vec>, fields: &FieldMap) -> Result { 31 | let mut output = FullQualityPaths { 32 | field_count: fields.len(), 33 | segs: Vec::new(), 34 | }; 35 | 36 | let need_sort = matches!(fields.iter().next(), Some((Member::Unnamed(_), _))); 37 | 38 | for path in origin_paths { 39 | fill_default(&mut output.segs, &path, fields)?; 40 | fill_path(&mut output.segs, path, fields)?; 41 | 42 | if need_sort { 43 | let len = output.segs.len(); 44 | output.segs[len - output.field_count..].sort_by(|(a, _), (b, _)| match (a, b) { 45 | (Member::Unnamed(ia), Member::Unnamed(ib)) => ia.index.cmp(&ib.index), 46 | _ => unreachable!("expect to be unnamed field"), 47 | }); 48 | } 49 | } 50 | 51 | Ok(output) 52 | } 53 | 54 | pub fn iter(&self) -> impl Iterator { 55 | let mut iter = if self.field_count == 0 { 56 | None 57 | } else { 58 | assert_eq!(self.segs.len() % self.field_count, 0); 59 | Some(self.segs.chunks(self.field_count)) 60 | }; 61 | 62 | std::iter::from_fn(move || iter.as_mut()?.next()) 63 | } 64 | } 65 | 66 | fn fill_default(segs: &mut SegmentVec, path: &[Segment], fields: &FieldMap) -> Result<()> { 67 | for field in fields { 68 | if path.iter().any(|seg| seg.tag() == field.0) { 69 | continue; 70 | } 71 | 72 | let value = match &field.1.default { 73 | Some(expr) => FullQualitySegment::Default(expr.clone()), 74 | None => { 75 | return Err(Error::new( 76 | field.0.span(), 77 | format!( 78 | "default behavior of field `{}` needs to be specified", 79 | tag_to_string(field.0) 80 | ), 81 | )) 82 | } 83 | }; 84 | 85 | segs.push((field.0.clone(), value)); 86 | } 87 | Ok(()) 88 | } 89 | 90 | fn fill_path(segs: &mut SegmentVec, path: Vec, fields: &FieldMap) -> Result<()> { 91 | for seg in path { 92 | let tag = seg.tag(); 93 | 94 | let Some(field) = fields.get(tag) 95 | else { 96 | return Err(Error::new(Span::call_site(), format!( 97 | "field `{}` doesn't exist, but it appears in `from` expression of style derive macros", 98 | tag_to_string(tag) 99 | ))); 100 | }; 101 | 102 | let value = match seg { 103 | Segment::Member(m) => (m, FullQualitySegment::Required(field.ty.clone())), 104 | Segment::Fn { 105 | bind, 106 | path, 107 | arg_types, 108 | } => ( 109 | bind, 110 | FullQualitySegment::Fn { 111 | fn_path: path, 112 | arg_types, 113 | }, 114 | ), 115 | }; 116 | 117 | segs.push(value); 118 | } 119 | Ok(()) 120 | } 121 | -------------------------------------------------------------------------------- /irisia-macros/src/derive_style/variant_analyzer/read_fields.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | parse_quote, spanned::Spanned, Error, Expr, Fields, FieldsNamed, FieldsUnnamed, Ident, Index, 3 | Member, Result, Type, 4 | }; 5 | 6 | use crate::derive_style::attributes::DeriveAttr; 7 | 8 | use super::get_attrs; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct FieldAnalysis { 12 | pub tag: Member, 13 | pub default: Option, 14 | pub ty: Type, 15 | pub option: Option, 16 | pub option_set_true: bool, 17 | pub skip_auto_from: bool, 18 | } 19 | 20 | impl FieldAnalysis { 21 | pub fn analyze(fields: &Fields) -> impl Iterator> + '_ { 22 | read_fields(fields).map(analyze_one) 23 | } 24 | } 25 | 26 | fn analyze_one(field: Result) -> Result { 27 | let field = field?; 28 | let mut default = None; 29 | let mut option = None; 30 | let mut option_set_true = false; 31 | let mut skip_auto_from = false; 32 | 33 | let error_duplicated = |msg| Err(Error::new_spanned(&field.member, msg)); 34 | 35 | for attr in field.attrs { 36 | match attr { 37 | DeriveAttr::Default { specified: expr } => { 38 | if default 39 | .replace( 40 | expr.unwrap_or_else(|| parse_quote!(::std::default::Default::default())), 41 | ) 42 | .is_some() 43 | { 44 | return error_duplicated("duplicated default declaration"); 45 | } 46 | } 47 | 48 | DeriveAttr::Option { rename, set_true } => { 49 | if option.is_some() { 50 | return error_duplicated("duplicated option declaration"); 51 | } 52 | 53 | option_set_true = set_true; 54 | 55 | match (&field.member, rename) { 56 | (Member::Named(id), None) => option = Some(id.clone()), 57 | (Member::Unnamed(index), None) => { 58 | return Err(Error::new_spanned(index, "option name is required")) 59 | } 60 | (_, name @ Some(_)) => option = name, 61 | } 62 | } 63 | 64 | DeriveAttr::Skip => { 65 | if skip_auto_from { 66 | return error_duplicated("duplicated skip declaration"); 67 | } 68 | skip_auto_from = true; 69 | } 70 | 71 | _ => continue, 72 | } 73 | } 74 | 75 | Ok(FieldAnalysis { 76 | tag: field.member, 77 | default, 78 | ty: field.ty, 79 | option, 80 | option_set_true, 81 | skip_auto_from, 82 | }) 83 | } 84 | 85 | struct ReadField { 86 | attrs: Vec, 87 | member: Member, 88 | ty: Type, 89 | } 90 | 91 | fn read_fields(fields: &Fields) -> impl Iterator> + '_ { 92 | let mut iter = match &fields { 93 | Fields::Named(FieldsNamed { named: fields, .. }) 94 | | Fields::Unnamed(FieldsUnnamed { 95 | unnamed: fields, .. 96 | }) => Some(fields.iter().enumerate()), 97 | Fields::Unit => None, 98 | }; 99 | 100 | std::iter::from_fn(move || { 101 | let iter = iter.as_mut()?; 102 | let (index, field) = iter.next()?; 103 | 104 | let member = match &field.ident { 105 | Some(ident) => Member::Named(ident.clone()), 106 | None => Member::Unnamed(Index { 107 | index: index as _, 108 | span: field.span(), 109 | }), 110 | }; 111 | 112 | Some(get_attrs(&field.attrs).map(move |attrs| ReadField { 113 | attrs, 114 | member, 115 | ty: field.ty.clone(), 116 | })) 117 | }) 118 | } 119 | -------------------------------------------------------------------------------- /irisia-macros/src/derive_style_reader.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use quote::{quote, ToTokens}; 3 | use syn::{ 4 | punctuated::{Pair, Punctuated}, 5 | Data, DeriveInput, Error, Field, Fields, Result, Token, 6 | }; 7 | 8 | pub fn derive_style_reader( 9 | DeriveInput { 10 | ident, 11 | generics, 12 | data, 13 | .. 14 | }: DeriveInput, 15 | ) -> Result { 16 | let fields = match data { 17 | Data::Struct(s) => s.fields, 18 | Data::Enum(_) | Data::Union(_) => { 19 | return Err(Error::new(Span::call_site(), "only struct is support")) 20 | } 21 | }; 22 | 23 | let inner = match fields { 24 | Fields::Named(named) => { 25 | let inner = fields_iter(named.named); 26 | quote! {{#inner}} 27 | } 28 | Fields::Unnamed(unnamed) => { 29 | let inner = fields_iter(unnamed.unnamed); 30 | quote! {(#inner)} 31 | } 32 | Fields::Unit => quote! {}, 33 | }; 34 | 35 | let (impl_gen, type_gen, where_clause) = generics.split_for_impl(); 36 | 37 | let output = quote! { 38 | impl #impl_gen irisia::StyleReader for #ident #type_gen 39 | #where_clause 40 | { 41 | fn read_style(_container: impl irisia::style::StyleContainer) -> Self { 42 | #ident #inner 43 | } 44 | } 45 | }; 46 | 47 | Ok(output) 48 | } 49 | 50 | fn fields_iter(fields: Punctuated) -> TokenStream { 51 | let mut tokens = TokenStream::new(); 52 | for pair in fields.pairs() { 53 | let ( 54 | Field { 55 | ident, 56 | colon_token, 57 | ty, 58 | .. 59 | }, 60 | comma, 61 | ) = match pair { 62 | Pair::Punctuated(t, p) => (t, Some(p)), 63 | Pair::End(t) => (t, None), 64 | }; 65 | 66 | quote! { 67 | #ident #colon_token <#ty as irisia::StyleReader>::read_style(&_container) #comma 68 | } 69 | .to_tokens(&mut tokens); 70 | } 71 | tokens 72 | } 73 | -------------------------------------------------------------------------------- /irisia-macros/src/element/build.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use syn::{parse::ParseStream, Result}; 3 | 4 | use crate::expr::state_block::{parse_stmts, stmts_to_tokens}; 5 | 6 | use super::ElementCodegen; 7 | 8 | pub fn build(input: ParseStream) -> Result { 9 | let stmts = parse_stmts::(input)?; 10 | Ok(stmts_to_tokens(&stmts)) 11 | } 12 | -------------------------------------------------------------------------------- /irisia-macros/src/element/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | 4 | use crate::expr::{enum_conditional, Codegen}; 5 | 6 | pub mod build; 7 | pub mod stmt; 8 | 9 | pub struct ElementCodegen; 10 | 11 | impl Codegen for ElementCodegen { 12 | type Command = Cmd; 13 | type Stmt = stmt::ElementStmt; 14 | 15 | const MUST_IN_BLOCK: bool = false; 16 | 17 | fn empty() -> TokenStream { 18 | quote!(()) 19 | } 20 | 21 | fn repetitive_applicate(iter: impl ToTokens) -> TokenStream { 22 | quote! { 23 | irisia::structure::Repeat(#iter) 24 | } 25 | } 26 | 27 | fn conditional_applicate(stmt: impl ToTokens, index: usize, total: usize) -> TokenStream { 28 | enum_conditional( 29 | quote!(irisia::structure::Branch::ArmA), 30 | quote!(irisia::structure::Branch::ArmB), 31 | stmt, 32 | index, 33 | total, 34 | ) 35 | } 36 | 37 | fn chain_applicate(prev: impl ToTokens, after: impl ToTokens) -> TokenStream { 38 | quote!(irisia::structure::Chain::new(#prev, #after)) 39 | } 40 | } 41 | 42 | pub struct Cmd; 43 | 44 | impl ToTokens for Cmd { 45 | fn to_tokens(&self, _: &mut proc_macro2::TokenStream) { 46 | unreachable!(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /irisia-macros/src/element/stmt/mod.rs: -------------------------------------------------------------------------------- 1 | use syn::{Expr, Ident, Type}; 2 | 3 | use crate::{element::ElementCodegen, expr::StateExpr}; 4 | 5 | mod parse; 6 | mod to_tokens; 7 | 8 | pub struct ElementStmt { 9 | element: Type, 10 | props: Vec<(Ident, Expr)>, 11 | style: Option, 12 | oncreate: Option, 13 | children: Vec>, 14 | } 15 | -------------------------------------------------------------------------------- /irisia-macros/src/element/stmt/parse.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | braced, 3 | parse::{Parse, ParseStream}, 4 | Error, Expr, Ident, Result, Token, Type, 5 | }; 6 | 7 | use crate::{element::stmt::ElementStmt, expr::state_block::parse_stmts}; 8 | 9 | impl Parse for ElementStmt { 10 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 11 | let element: Type = input.parse()?; 12 | let mut props: Vec<(Ident, Expr)> = Vec::new(); 13 | let mut style: Option = None; 14 | let mut oncreate: Option = None; 15 | 16 | let content; 17 | braced!(content in input); 18 | 19 | while !content.is_empty() { 20 | if content.peek(Ident) && content.peek2(Token![:]) { 21 | // parse props-value pair 22 | props.push((content.parse()?, { 23 | content.parse::()?; 24 | content.parse()? 25 | })); 26 | } else if content.peek(Token![+]) { 27 | content.parse::()?; 28 | let cmd: Ident = content.parse()?; 29 | content.parse::()?; 30 | 31 | let is_ok = match &*cmd.to_string() { 32 | "style" => style.replace(call_style(&content)?).is_none(), 33 | "oncreate" => oncreate.replace(call_oncreate(&content)?).is_none(), 34 | other => { 35 | return Err(Error::new(cmd.span(), format!("unknown command `{other}`"))) 36 | } 37 | }; 38 | 39 | if !is_ok { 40 | return Err(Error::new( 41 | cmd.span(), 42 | format!("duplicated command `{}`", cmd), 43 | )); 44 | } 45 | } else { 46 | break; 47 | } 48 | 49 | if !content.is_empty() { 50 | content.parse::()?; 51 | } 52 | } 53 | 54 | let children = parse_stmts(&content)?; 55 | 56 | Ok(ElementStmt { 57 | element, 58 | props, 59 | style, 60 | oncreate, 61 | children, 62 | }) 63 | } 64 | } 65 | 66 | fn call_style(input: ParseStream) -> Result { 67 | input.parse() 68 | } 69 | 70 | fn call_oncreate(input: ParseStream) -> Result { 71 | input.parse() 72 | } 73 | -------------------------------------------------------------------------------- /irisia-macros/src/element/stmt/to_tokens.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{parse_quote, Expr, Ident, Type}; 4 | 5 | use crate::expr::state_block::stmts_to_tokens; 6 | 7 | use super::ElementStmt; 8 | 9 | impl ToTokens for ElementStmt { 10 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 11 | let ElementStmt { 12 | element, 13 | props, 14 | style, 15 | oncreate, 16 | children, 17 | } = self; 18 | 19 | let props = gen_props(element, props); 20 | let style = match style { 21 | Some(style) => style.into_token_stream(), 22 | None => quote!(()), 23 | }; 24 | let children = stmts_to_tokens(children); 25 | let oncreate = oncreate.clone().unwrap_or_else(|| parse_quote!(|_: &_| {})); 26 | 27 | quote! { 28 | irisia::structure::Once( 29 | irisia::element::one_child::<#element, _, _, _, _>( 30 | #props, 31 | #style, 32 | #children, 33 | #oncreate, 34 | ) 35 | ) 36 | } 37 | .to_tokens(tokens) 38 | } 39 | } 40 | 41 | fn gen_props(element: &Type, pairs: &[(Ident, Expr)]) -> TokenStream { 42 | let props = pairs.iter().map(|x| &x.0); 43 | let exprs = pairs.iter().map(|x| &x.1); 44 | 45 | quote! { 46 | <<#element as irisia::element::Element>::BlankProps as ::std::default::Default>::default() 47 | #(.#props(#exprs))* 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /irisia-macros/src/expr/conditional/mod.rs: -------------------------------------------------------------------------------- 1 | use quote::ToTokens; 2 | 3 | use super::{Codegen, StateExpr, StateIf, StateMatch}; 4 | 5 | pub mod state_if; 6 | pub mod state_match; 7 | 8 | pub enum StateConditional { 9 | If(StateIf), 10 | Match(StateMatch), 11 | } 12 | 13 | impl StateConditional { 14 | pub fn arms(&self) -> impl Iterator]> { 15 | enum Branch { 16 | T(T), 17 | U(U), 18 | } 19 | 20 | let mut iter = match self { 21 | Self::If(i) => Branch::T(i.arms()), 22 | Self::Match(m) => Branch::U(m.arms()), 23 | }; 24 | 25 | std::iter::from_fn(move || match &mut iter { 26 | Branch::T(i) => i.next().map(|block| &*block.stmts), 27 | Branch::U(m) => m.next().map(|expr| std::array::from_ref(expr) as _), 28 | }) 29 | } 30 | } 31 | 32 | impl ToTokens for StateConditional { 33 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 34 | match self { 35 | Self::If(i) => i.to_tokens(tokens), 36 | Self::Match(m) => m.to_tokens(tokens), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /irisia-macros/src/expr/conditional/state_if.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{parse::Parse, token::Brace, Expr, Token}; 4 | 5 | use crate::expr::{state_block::StateBlock, Codegen}; 6 | 7 | /* 8 | // count 4 9 | if expr { 10 | Arm1(xxx) // skipped 0 11 | } else if expr2 { 12 | Arm2(Arm1(xxx)) // skipped 1 13 | } else if expr3 { 14 | Arm2(Arm2(Arm1(xxx))) // skipped 2 15 | } else { 16 | Arm2(Arm2(Arm2(xxx))) // skipped 3 17 | } 18 | */ 19 | pub struct StateIf { 20 | leading_if: If, 21 | else_ifs: Vec<(Token![else], If)>, 22 | default_else: Option, 23 | default: StateBlock, 24 | } 25 | 26 | struct If { 27 | if_token: Token![if], 28 | cond: Expr, 29 | then: StateBlock, 30 | } 31 | 32 | impl StateIf { 33 | pub fn arms(&self) -> impl Iterator> { 34 | std::iter::once(&self.leading_if.then) 35 | .chain(self.else_ifs.iter().map(|(_, i)| &i.then)) 36 | .chain(std::iter::once(&self.default)) 37 | } 38 | } 39 | 40 | impl Parse for StateIf { 41 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 42 | let leading_if = input.parse()?; 43 | 44 | let mut else_ifs = Vec::new(); 45 | let mut default: Option<(Token![else], StateBlock)> = None; 46 | 47 | while input.peek(Token![else]) { 48 | let else_token = input.parse()?; 49 | if input.peek2(Token![if]) { 50 | else_ifs.push((else_token, input.parse()?)); 51 | } else { 52 | default = Some((else_token, input.parse()?)); 53 | break; 54 | } 55 | } 56 | 57 | let (default_else, default) = match default { 58 | Some((et, df)) => (Some(et), df), 59 | None => Default::default(), 60 | }; 61 | 62 | Ok(StateIf { 63 | leading_if, 64 | else_ifs, 65 | default_else, 66 | default, 67 | }) 68 | } 69 | } 70 | 71 | impl Parse for If { 72 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 73 | Ok(If { 74 | if_token: input.parse()?, 75 | cond: Expr::parse_without_eager_brace(input)?, 76 | then: input.parse()?, 77 | }) 78 | } 79 | } 80 | 81 | impl If { 82 | fn as_branch(&self, tokens: &mut TokenStream, index: usize, total: usize) { 83 | let If { 84 | if_token, 85 | cond, 86 | then, 87 | } = self; 88 | 89 | let then = T::conditional_applicate(then, index, total); 90 | 91 | tokens.extend(quote! { 92 | #if_token #cond { 93 | #then 94 | } 95 | }); 96 | } 97 | } 98 | 99 | impl ToTokens for StateIf { 100 | fn to_tokens(&self, tokens: &mut TokenStream) { 101 | let mut index = 0; 102 | 103 | // +2: leading if and trailing else 104 | let total = self.else_ifs.len() + 2; 105 | 106 | self.leading_if.as_branch(tokens, index, total); 107 | index += 1; 108 | 109 | for (else_token, if_expr) in self.else_ifs.iter() { 110 | else_token.to_tokens(tokens); 111 | if_expr.as_branch(tokens, index, total); 112 | index += 1; 113 | } 114 | 115 | match &self.default_else { 116 | Some(t) => t.to_tokens(tokens), 117 | None => ::default().to_tokens(tokens), 118 | } 119 | 120 | Brace::default().surround(tokens, |tokens| { 121 | tokens.extend(T::conditional_applicate(&self.default, total - 1, total)) 122 | }); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /irisia-macros/src/expr/conditional/state_match.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::ToTokens; 3 | use syn::{ 4 | braced, 5 | parse::{Nothing, Parse}, 6 | punctuated::Punctuated, 7 | token::Brace, 8 | Expr, Pat, Token, 9 | }; 10 | 11 | use crate::expr::{Codegen, StateExpr}; 12 | 13 | /* 14 | match expr1 { 15 | Pat1(x) if expr2 => Arm1(...), 16 | Pat2(y) => Arm2(Arm1(...)), 17 | Pat3(z) => Arm2(Arm2(...)), 18 | } 19 | */ 20 | pub struct StateMatch { 21 | match_token: Token![match], 22 | expr: Expr, 23 | brace_token: Brace, 24 | arms: Vec>, 25 | } 26 | 27 | pub struct Arm { 28 | pat: Pat, 29 | guard: Option<(Token![if], Expr)>, 30 | fat_arrow_token: Token![=>], 31 | body: StateExpr, 32 | comma: Option, 33 | } 34 | 35 | impl StateMatch { 36 | pub fn arms(&self) -> impl Iterator> { 37 | self.arms.iter().map(|arm| &arm.body) 38 | } 39 | } 40 | 41 | impl Parse for StateMatch { 42 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 43 | let match_token = input.parse()?; 44 | let expr = Expr::parse_without_eager_brace(input)?; 45 | 46 | let match_body; 47 | let brace_token = braced!(match_body in input); 48 | let arms = Punctuated::<_, Nothing>::parse_terminated_with(&match_body, |input| { 49 | Ok(Arm { 50 | pat: Pat::parse_multi_with_leading_vert(input)?, 51 | guard: { 52 | if input.peek(Token![if]) { 53 | Some((input.parse()?, input.parse()?)) 54 | } else { 55 | None 56 | } 57 | }, 58 | fat_arrow_token: input.parse()?, 59 | body: { 60 | let body: StateExpr = input.parse()?; 61 | if matches!(body, StateExpr::Raw(_)) && T::MUST_IN_BLOCK { 62 | return Err(input.error( 63 | "expression must be in block, consider move it into braces: `{expression}`.", 64 | )); 65 | } 66 | body 67 | }, 68 | comma: input.parse()? 69 | }) 70 | })? 71 | .into_iter() 72 | .collect(); 73 | 74 | Ok(StateMatch { 75 | match_token, 76 | expr, 77 | brace_token, 78 | arms, 79 | }) 80 | } 81 | } 82 | 83 | impl ToTokens for StateMatch { 84 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 85 | let StateMatch { 86 | match_token, 87 | expr, 88 | brace_token: _, 89 | arms, 90 | } = self; 91 | 92 | match_token.to_tokens(tokens); 93 | expr.to_tokens(tokens); 94 | self.brace_token.surround(tokens, |tokens| { 95 | let total = arms.len(); 96 | for (index, arm) in arms.iter().enumerate() { 97 | let mut t = TokenStream::new(); 98 | arm_to_tokens(&mut t, arm, index, total); 99 | *tokens = T::chain_applicate(&tokens, t); 100 | } 101 | }); 102 | } 103 | } 104 | 105 | // pat if guard => expr, 106 | fn arm_to_tokens(tokens: &mut TokenStream, arm: &Arm, index: usize, total: usize) { 107 | let Arm { 108 | pat, 109 | guard, 110 | fat_arrow_token, 111 | body, 112 | comma, 113 | } = arm; 114 | 115 | pat.to_tokens(tokens); 116 | 117 | if let Some((if_token, expr)) = guard { 118 | if_token.to_tokens(tokens); 119 | expr.to_tokens(tokens); 120 | } 121 | 122 | fat_arrow_token.to_tokens(tokens); 123 | tokens.extend(T::conditional_applicate(body, index, total)); 124 | 125 | match comma { 126 | Some(c) => c.to_tokens(tokens), 127 | None => ::default().to_tokens(tokens), 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /irisia-macros/src/expr/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{ 4 | parse::{Parse, ParseStream}, 5 | token::Brace, 6 | Result, Token, 7 | }; 8 | 9 | pub use self::{ 10 | conditional::{state_if::StateIf, state_match::StateMatch}, 11 | repetitive::StateRepetitive, 12 | state_block::StateBlock, 13 | state_command::{StateCommand, StateCommandBody}, 14 | state_expr::StateExpr, 15 | }; 16 | 17 | pub mod conditional; 18 | pub mod repetitive; 19 | pub mod state_block; 20 | pub mod state_command; 21 | pub mod state_expr; 22 | 23 | pub trait Codegen { 24 | type Stmt: ToTokens + Parse; 25 | type Command; 26 | 27 | const MUST_IN_BLOCK: bool; 28 | 29 | fn parse_command(_cmd: &str, _input: ParseStream) -> Result> { 30 | Ok(None) 31 | } 32 | 33 | fn empty() -> TokenStream; 34 | 35 | fn command_applicate(_cmd: &Self::Command) -> Option { 36 | None 37 | } 38 | 39 | fn conditional_applicate(stmt: impl ToTokens, index: usize, total: usize) -> TokenStream; 40 | 41 | fn repetitive_applicate(stmt: impl ToTokens) -> TokenStream; 42 | 43 | fn chain_applicate(prev: impl ToTokens, after: impl ToTokens) -> TokenStream; 44 | } 45 | 46 | pub fn enum_conditional( 47 | branch1: impl ToTokens, 48 | branch2: impl ToTokens, 49 | stmt: impl ToTokens, 50 | index: usize, 51 | total: usize, 52 | ) -> TokenStream { 53 | // if it is the last branch 54 | let mut output = if index == total - 1 { 55 | stmt.to_token_stream() 56 | } else { 57 | quote!(#branch1(#stmt)) 58 | }; 59 | 60 | for _ in 0..index { 61 | output = quote!(#branch2(#output)); 62 | } 63 | 64 | output 65 | } 66 | -------------------------------------------------------------------------------- /irisia-macros/src/expr/repetitive/mod.rs: -------------------------------------------------------------------------------- 1 | use quote::ToTokens; 2 | 3 | pub use self::{state_for::StateForLoop, state_while::StateWhile}; 4 | use super::{Codegen, StateBlock}; 5 | 6 | pub mod state_for; 7 | pub mod state_while; 8 | 9 | pub enum StateRepetitive { 10 | For(StateForLoop), 11 | While(StateWhile), 12 | } 13 | 14 | impl StateRepetitive { 15 | pub fn body(&self) -> &StateBlock { 16 | match self { 17 | Self::For(f) => &f.body, 18 | Self::While(w) => &w.state_block, 19 | } 20 | } 21 | } 22 | 23 | impl ToTokens for StateRepetitive { 24 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 25 | let expr_iter = match self { 26 | Self::For(f) => f.expr_iter(), 27 | Self::While(w) => w.expr_iter(), 28 | }; 29 | 30 | tokens.extend(T::repetitive_applicate(expr_iter)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /irisia-macros/src/expr/repetitive/state_for.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{parse::Parse, Expr, Pat, Token}; 4 | 5 | use crate::expr::{state_block::StateBlock, Codegen}; 6 | 7 | pub struct StateForLoop { 8 | pub pat: Pat, 9 | pub iter: Expr, 10 | pub key: Option, 11 | pub body: StateBlock, 12 | } 13 | 14 | impl Parse for StateForLoop { 15 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 16 | input.parse::()?; 17 | let pat = Pat::parse_multi_with_leading_vert(input)?; 18 | input.parse::()?; 19 | let iter = Expr::parse_without_eager_brace(input)?; 20 | let body: StateBlock = input.parse()?; 21 | let key = body.get_key()?.cloned(); 22 | 23 | Ok(StateForLoop { 24 | pat, 25 | iter, 26 | key, 27 | body, 28 | }) 29 | } 30 | } 31 | 32 | impl StateForLoop { 33 | pub(super) fn expr_iter(&self) -> TokenStream { 34 | let StateForLoop { 35 | pat, 36 | iter, 37 | body, 38 | key, 39 | } = self; 40 | 41 | match key { 42 | Some(k) => quote! { 43 | ::std::iter::Iterator::map( 44 | #iter, 45 | |#pat| (#k, #body) 46 | ) 47 | }, 48 | None => quote! { 49 | irisia::__private::for_loop_iter_item_as_key(#iter, |#pat| #body) 50 | }, 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /irisia-macros/src/expr/repetitive/state_while.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{parse::Parse, Expr, Token}; 4 | 5 | use crate::expr::{state_block::StateBlock, Codegen}; 6 | 7 | pub struct StateWhile { 8 | pub cond: Expr, 9 | pub key: Expr, 10 | pub state_block: StateBlock, 11 | } 12 | 13 | impl Parse for StateWhile { 14 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 15 | input.parse::()?; 16 | let cond = Expr::parse_without_eager_brace(input)?; 17 | let state_block: StateBlock = input.parse()?; 18 | let key = state_block.get_key()?; 19 | 20 | match key { 21 | Some(k) => Ok(StateWhile { 22 | cond, 23 | key: k.clone(), 24 | state_block, 25 | }), 26 | None => Err(input.error("missing key declaration. consider add a `@key ...;` command")), 27 | } 28 | } 29 | } 30 | 31 | impl StateWhile { 32 | pub(super) fn expr_iter(&self) -> TokenStream { 33 | let StateWhile { 34 | cond, 35 | key, 36 | state_block, 37 | } = self; 38 | 39 | quote! { 40 | ::std::iter::from_fn( 41 | || if #cond { 42 | ::std::option::Option::Some(( 43 | #key, 44 | #state_block 45 | )) 46 | } else { 47 | ::std::option::Option::None 48 | } 49 | ) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /irisia-macros/src/expr/state_block.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::ToTokens; 3 | use syn::{ 4 | braced, 5 | parse::{Nothing, Parse, ParseStream}, 6 | punctuated::Punctuated, 7 | spanned::Spanned, 8 | token::Brace, 9 | Expr, Result, Token, 10 | }; 11 | 12 | use super::{ 13 | state_command::{StateCommand, StateCommandBody}, 14 | Codegen, StateExpr, 15 | }; 16 | 17 | // {Empty.xxx().xxx()} 18 | pub struct StateBlock { 19 | pub brace: Brace, 20 | pub stmts: Vec>, 21 | } 22 | 23 | impl Default for StateBlock { 24 | fn default() -> Self { 25 | StateBlock { 26 | brace: Default::default(), 27 | stmts: Vec::new(), 28 | } 29 | } 30 | } 31 | 32 | impl Parse for StateBlock { 33 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 34 | let states; 35 | let brace = braced!(states in input); 36 | Ok(StateBlock { 37 | brace, 38 | stmts: parse_stmts(&states)?, 39 | }) 40 | } 41 | } 42 | 43 | impl ToTokens for StateBlock { 44 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 45 | self.brace.surround(tokens, |tokens| { 46 | tokens.extend(stmts_to_tokens(&self.stmts)); 47 | }); 48 | } 49 | } 50 | 51 | impl StateBlock { 52 | pub fn get_key(&self) -> Result> { 53 | let mut key = None; 54 | for stmt in &self.stmts { 55 | if let StateExpr::Command(StateCommand { 56 | body: StateCommandBody::Key(key_expr), 57 | .. 58 | }) = stmt 59 | { 60 | if key.replace(key_expr).is_some() { 61 | return Err(syn::Error::new( 62 | key_expr.span(), 63 | "duplicated key declaration", 64 | )); 65 | } 66 | } 67 | } 68 | Ok(key) 69 | } 70 | } 71 | 72 | pub fn parse_stmts(input: ParseStream) -> Result>> { 73 | let vec = Punctuated::, Nothing>::parse_terminated_with(input, |input| { 74 | let expr = input.parse()?; 75 | let _ = input.parse::>(); 76 | Ok(expr) 77 | })? 78 | .into_iter() 79 | .collect(); 80 | Ok(vec) 81 | } 82 | 83 | pub fn stmts_to_tokens(stmts: &[StateExpr]) -> TokenStream { 84 | let mut tokens = T::empty(); 85 | for expr in stmts { 86 | tokens = T::chain_applicate(tokens, expr); 87 | } 88 | tokens 89 | } 90 | -------------------------------------------------------------------------------- /irisia-macros/src/expr/state_command.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span}; 2 | use quote::ToTokens; 3 | use syn::{ 4 | parse::{Parse, ParseStream}, 5 | Error, Expr, Result, Token, 6 | }; 7 | 8 | use super::Codegen; 9 | 10 | pub struct StateCommand { 11 | pub span: Span, 12 | pub body: StateCommandBody, 13 | } 14 | 15 | pub enum StateCommandBody { 16 | Key(Expr), 17 | Custom(T::Command), 18 | } 19 | 20 | impl Parse for StateCommand { 21 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 22 | input.parse::()?; 23 | let cmd_ident: Ident = input.parse()?; 24 | 25 | let cmd = match &*cmd_ident.to_string() { 26 | "key" => parse_key(input)?, 27 | other => match T::parse_command(other, input)? { 28 | Some(c) => StateCommandBody::Custom(c), 29 | None => { 30 | return Err(Error::new( 31 | cmd_ident.span(), 32 | format!("unknown command `{other}`"), 33 | )) 34 | } 35 | }, 36 | }; 37 | input.parse::()?; 38 | 39 | Ok(StateCommand { 40 | span: cmd_ident.span(), 41 | body: cmd, 42 | }) 43 | } 44 | } 45 | 46 | impl ToTokens for StateCommand { 47 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 48 | if let StateCommandBody::Custom(other) = &self.body { 49 | if let Some(t) = T::command_applicate(other) { 50 | tokens.extend(t); 51 | return; 52 | } 53 | } 54 | 55 | tokens.extend(T::empty()); 56 | } 57 | } 58 | 59 | fn parse_key(input: ParseStream) -> Result> { 60 | Ok(StateCommandBody::Key(input.parse()?)) 61 | } 62 | -------------------------------------------------------------------------------- /irisia-macros/src/expr/state_expr.rs: -------------------------------------------------------------------------------- 1 | use super::{conditional::StateConditional, *}; 2 | 3 | pub enum StateExpr 4 | where 5 | T: Codegen, 6 | { 7 | Raw(T::Stmt), 8 | Block(StateBlock), 9 | Conditional(StateConditional), 10 | Repetitive(StateRepetitive), 11 | Command(StateCommand), 12 | } 13 | 14 | impl Parse for StateExpr { 15 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 16 | let r = if input.peek(Token![if]) { 17 | StateExpr::Conditional(StateConditional::If(input.parse()?)) 18 | } else if input.peek(Token![match]) { 19 | StateExpr::Conditional(StateConditional::Match(input.parse()?)) 20 | } else if input.peek(Token![while]) { 21 | StateExpr::Repetitive(StateRepetitive::While(input.parse()?)) 22 | } else if input.peek(Token![for]) { 23 | StateExpr::Repetitive(StateRepetitive::For(input.parse()?)) 24 | } else if input.peek(Brace) { 25 | StateExpr::Block(input.parse()?) 26 | } else if input.peek(Token!(@)) { 27 | StateExpr::Command(input.parse()?) 28 | } else { 29 | StateExpr::Raw(input.parse()?) 30 | }; 31 | Ok(r) 32 | } 33 | } 34 | 35 | macro_rules! impl_to_tokens { 36 | ($($Arm:ident)*) => { 37 | impl ToTokens for StateExpr { 38 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 39 | match self { 40 | $(StateExpr::$Arm(x) => x.to_tokens(tokens),)* 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | impl_to_tokens!(Raw Block Conditional Repetitive Command); 48 | 49 | macro_rules! impl_from { 50 | ($($Arm:ident $Type:ident,)*) => { 51 | $( 52 | impl From<$Type> for StateExpr { 53 | fn from(e: $Type) -> Self { 54 | Self::$Arm(e) 55 | } 56 | } 57 | )* 58 | }; 59 | } 60 | 61 | impl_from! { 62 | Block StateBlock, 63 | Conditional StateConditional, 64 | Repetitive StateRepetitive, 65 | Command StateCommand, 66 | } 67 | -------------------------------------------------------------------------------- /irisia-macros/src/main_macro.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::ToTokens; 3 | use syn::{parse_quote, Error, ItemFn, Result}; 4 | 5 | pub fn main_macro(mut item: ItemFn) -> Result { 6 | if item.sig.asyncness.take().is_none() { 7 | return Err(Error::new_spanned( 8 | &item.sig, 9 | "function is expected to be asynchronous", 10 | )); 11 | } 12 | 13 | let block = &item.block; 14 | 15 | item.block = parse_quote! {{ 16 | irisia::start_runtime( 17 | async move #block 18 | ) 19 | }}; 20 | 21 | Ok(item.into_token_stream()) 22 | } 23 | -------------------------------------------------------------------------------- /irisia-macros/src/style/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{parse::ParseStream, Result}; 4 | 5 | use crate::expr::{enum_conditional, state_block::parse_stmts, Codegen, StateExpr}; 6 | 7 | use self::stmt::{handle_style_follow, StyleStmt}; 8 | 9 | pub mod stmt; 10 | 11 | pub struct StyleCommand(()); 12 | 13 | impl ToTokens for StyleCommand { 14 | fn to_tokens(&self, _tokens: &mut TokenStream) { 15 | unreachable!(); 16 | } 17 | } 18 | 19 | pub struct StyleCodegen; 20 | 21 | impl Codegen for StyleCodegen { 22 | type Command = StyleCommand; 23 | type Stmt = StyleStmt; 24 | 25 | const MUST_IN_BLOCK: bool = true; 26 | 27 | fn parse_command(_cmd: &str, _input: ParseStream) -> syn::Result> { 28 | Ok(None) 29 | } 30 | 31 | fn empty() -> TokenStream { 32 | quote!(()) 33 | } 34 | 35 | fn repetitive_applicate(_: impl ToTokens) -> TokenStream { 36 | quote!(::std::compile_error!( 37 | "repetitive structure is not allowed in style macro" 38 | )) 39 | } 40 | 41 | fn conditional_applicate(stmt: impl ToTokens, index: usize, total: usize) -> TokenStream { 42 | enum_conditional( 43 | quote!(irisia::style::Branch::ArmA), 44 | quote!(irisia::style::Branch::ArmB), 45 | stmt, 46 | index, 47 | total, 48 | ) 49 | } 50 | 51 | fn chain_applicate(prev: impl ToTokens, after: impl ToTokens) -> TokenStream { 52 | quote!(irisia::style::Chain::new(#prev, #after)) 53 | } 54 | } 55 | 56 | pub fn style(input: ParseStream) -> Result>> { 57 | let mut stmts = parse_stmts(input)?; 58 | handle_style_follow(&mut stmts)?; 59 | Ok(stmts) 60 | } 61 | -------------------------------------------------------------------------------- /irisia-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "irisia_utils" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | thiserror = "1.0" 10 | tokio = { version = "1.28", features = ["sync"] } 11 | -------------------------------------------------------------------------------- /irisia-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use reuse_vec::ReuseVec; 2 | 3 | pub mod reuse_vec; 4 | -------------------------------------------------------------------------------- /irisia-utils/src/reuse_vec.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | alloc::{dealloc, Layout}, 3 | mem::ManuallyDrop, 4 | }; 5 | 6 | use thiserror::Error; 7 | 8 | #[derive(Debug)] 9 | pub struct ReuseVec { 10 | ptr: *mut (), 11 | layout: Layout, 12 | capacity: usize, 13 | } 14 | 15 | impl From> for ReuseVec { 16 | fn from(value: Vec) -> Self { 17 | let mut vec = ManuallyDrop::new(value); 18 | vec.clear(); // Ensures nothing can escape from being destructed 19 | ReuseVec { 20 | ptr: vec.as_mut_ptr().cast(), 21 | layout: Layout::new::(), 22 | capacity: vec.capacity(), 23 | } 24 | } 25 | } 26 | 27 | impl ReuseVec { 28 | pub fn new() -> Self { 29 | Self::from(Vec::<()>::new()) 30 | } 31 | 32 | pub fn into_vec(self) -> Vec { 33 | self.try_into_vec().unwrap_or_default() 34 | } 35 | 36 | pub fn try_into_vec(self) -> Result, ReuseVecLayoutError> { 37 | if self.capacity == 0 { 38 | return Ok(Vec::new()); 39 | } 40 | 41 | let this_layout = Layout::new::(); 42 | if self.layout != this_layout { 43 | return Err(ReuseVecLayoutError { 44 | expected: self.layout, 45 | provided: this_layout, 46 | reuse_vec: self, 47 | }); 48 | } 49 | 50 | let this = ManuallyDrop::new(self); 51 | unsafe { Ok(Vec::from_raw_parts(this.ptr.cast(), 0, this.capacity)) } 52 | } 53 | } 54 | 55 | impl From for Vec { 56 | fn from(this: ReuseVec) -> Self { 57 | this.into_vec() 58 | } 59 | } 60 | 61 | impl Default for ReuseVec { 62 | fn default() -> Self { 63 | Self::new() 64 | } 65 | } 66 | 67 | impl Drop for ReuseVec { 68 | fn drop(&mut self) { 69 | let size = self.layout.size() * self.capacity; 70 | 71 | if size != 0 { 72 | let layout = Layout::from_size_align(size, self.layout.align()).unwrap(); 73 | unsafe { 74 | dealloc(self.ptr.cast(), layout); 75 | } 76 | } 77 | } 78 | } 79 | 80 | #[derive(Debug, Error)] 81 | #[error( 82 | "cannot convert `ReuseVec` to `Vec`, because the memory layout doesn't match. \ 83 | expected: {expected:?}, \ 84 | provided: {provided:?}" 85 | )] 86 | pub struct ReuseVecLayoutError { 87 | pub reuse_vec: ReuseVec, 88 | pub expected: Layout, 89 | pub provided: Layout, 90 | } 91 | -------------------------------------------------------------------------------- /irisia-widgets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "irisia_widgets" 3 | version = "1.0.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | irisia = { path = "../irisia" } 10 | smallvec = "1.10" 11 | tokio = { version = "1.27", features = ["sync"] } 12 | lazy_static = "1" 13 | -------------------------------------------------------------------------------- /irisia-widgets/src/box_styles/border.rs: -------------------------------------------------------------------------------- 1 | use irisia::{ 2 | primitive::Pixel, 3 | skia_safe::{ 4 | paint::Cap, Canvas, Color, Color4f, ColorSpace, Paint, PaintStyle, PathEffect, 5 | Point as SkiaPoint, RRect, 6 | }, 7 | Style, 8 | }; 9 | use smallvec::SmallVec; 10 | 11 | #[derive(Style, Clone)] 12 | #[style( 13 | impl_default, 14 | from = "width, [color,] [style | style: sliced_style(&'static [Pixel], Pixel)]" 15 | )] 16 | pub struct StyleBorder { 17 | #[style(default)] 18 | pub width: Pixel, 19 | 20 | #[style(default = "Color::BLACK", option)] 21 | pub color: Color, 22 | 23 | #[style(default = "DashStyle::Solid", option)] 24 | pub style: DashStyle, 25 | 26 | #[style(default = "Cap::Square", option)] 27 | pub cap: Cap, 28 | } 29 | 30 | #[derive(Clone)] 31 | pub enum DashStyle { 32 | Owned { 33 | intervals: SmallVec<[Pixel; 8]>, 34 | phase: Pixel, 35 | }, 36 | Sliced { 37 | intervals: &'static [Pixel], 38 | phase: Pixel, 39 | }, 40 | Solid, 41 | Dotted, 42 | } 43 | 44 | fn sliced_style(intervals: &'static [Pixel], phase: Pixel) -> DashStyle { 45 | DashStyle::Sliced { intervals, phase } 46 | } 47 | 48 | impl StyleBorder { 49 | pub fn solid(&mut self) { 50 | self.style(DashStyle::Solid); 51 | } 52 | 53 | pub fn dotted(&mut self) { 54 | self.style(DashStyle::Dotted); 55 | } 56 | 57 | pub fn butt_cap(&mut self) { 58 | self.cap = Cap::Butt; 59 | } 60 | 61 | pub fn round_cap(&mut self) { 62 | self.cap = Cap::Round; 63 | } 64 | 65 | pub fn square_cap(&mut self) { 66 | self.cap = Cap::Square; 67 | } 68 | } 69 | 70 | // returns stroke width 71 | pub(super) fn draw_border(canvas: &mut Canvas, mut rrect: RRect, border: &StyleBorder) -> f32 { 72 | let stroke_width = border.width.to_physical(); 73 | 74 | let size_reduction = stroke_width / 2.0; 75 | rrect.inset(SkiaPoint::new(size_reduction, size_reduction)); 76 | 77 | let mut paint = Paint::new(Color4f::from(border.color), &ColorSpace::new_srgb()); 78 | paint 79 | .set_style(PaintStyle::Stroke) 80 | .set_stroke_width(stroke_width) 81 | .set_stroke_cap(border.cap) 82 | .set_anti_alias(true) 83 | .set_path_effect(parse_dash_style(&border.style, stroke_width)); 84 | 85 | canvas.draw_rrect(&rrect, &paint); 86 | 87 | stroke_width 88 | } 89 | 90 | fn parse_dash_style(style: &DashStyle, stroke_width: f32) -> Option { 91 | fn slice_dash(intervals: &[Pixel], phase: &Pixel) -> Option { 92 | let vec: SmallVec<[f32; 12]> = intervals.iter().map(|px| px.to_physical()).collect(); 93 | PathEffect::dash(&vec, phase.to_physical()) 94 | } 95 | match style { 96 | DashStyle::Solid => None, 97 | DashStyle::Owned { intervals, phase } => slice_dash(&intervals, phase), 98 | DashStyle::Sliced { intervals, phase } => slice_dash(intervals, phase), 99 | DashStyle::Dotted => PathEffect::dash(&[stroke_width, 20.0], 0.0), 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /irisia-widgets/src/box_styles/border_clip.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fancyflame/irisia-gui/9f98a6ce5f14ae1ae18295bb041000c370b43c35/irisia-widgets/src/box_styles/border_clip.rs -------------------------------------------------------------------------------- /irisia-widgets/src/box_styles/border_radius.rs: -------------------------------------------------------------------------------- 1 | use irisia::{primitive::Pixel, Style}; 2 | use irisia::skia_safe::{Point as SkiaPoint, RRect, Rect}; 3 | 4 | #[derive(Style, Clone)] 5 | pub enum StyleBorderRadius { 6 | #[style(from)] 7 | Radii(Pixel), 8 | 9 | #[style(from, from = "", impl_default)] 10 | Radii4 { 11 | #[style(option, default)] 12 | left_top: Pixel, 13 | 14 | #[style(option, default)] 15 | right_top: Pixel, 16 | 17 | #[style(option, default)] 18 | right_bottom: Pixel, 19 | 20 | #[style(option, default)] 21 | left_bottom: Pixel, 22 | }, 23 | 24 | Oval, 25 | } 26 | 27 | impl StyleBorderRadius { 28 | pub fn oval(&mut self) { 29 | *self = StyleBorderRadius::Oval; 30 | } 31 | } 32 | 33 | pub(super) fn parse_border_radius(rect: &Rect, border_radius: &StyleBorderRadius) -> RRect { 34 | match border_radius { 35 | StyleBorderRadius::Radii(r) => RRect::new_rect_xy(&rect, r.to_physical(), r.to_physical()), 36 | StyleBorderRadius::Oval => RRect::new_oval(&rect), 37 | StyleBorderRadius::Radii4 { 38 | left_top, 39 | right_top, 40 | right_bottom, 41 | left_bottom, 42 | } => { 43 | fn convert(point: &Pixel) -> SkiaPoint { 44 | SkiaPoint::new(point.to_physical(), point.to_physical()) 45 | } 46 | 47 | RRect::new_rect_radii( 48 | &rect, 49 | &[ 50 | convert(left_top), 51 | convert(right_top), 52 | convert(right_bottom), 53 | convert(left_bottom), 54 | ], 55 | ) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /irisia-widgets/src/box_styles/box_shadow.rs: -------------------------------------------------------------------------------- 1 | use irisia::{primitive::Pixel, skia_safe::Color, Style}; 2 | use irisia::skia_safe::{ 3 | canvas::SaveLayerRec, BlendMode, BlurStyle, Canvas, Color4f, ColorSpace, MaskFilter, Paint, 4 | RRect, 5 | }; 6 | 7 | #[derive(Style, Clone)] 8 | #[style(from = "radius, [spread,] [color]")] 9 | pub struct StyleBoxShadow { 10 | pub radius: Pixel, 11 | 12 | #[style(default)] 13 | pub spread: Pixel, 14 | 15 | #[style(default = "Color::BLACK")] 16 | pub color: Color, 17 | } 18 | 19 | pub(super) fn draw_shadow(canvas: &mut Canvas, rrect: &RRect, style: &StyleBoxShadow) { 20 | let mask_filter = MaskFilter::blur(BlurStyle::Solid, style.radius.to_physical(), true); 21 | 22 | let mut paint = Paint::new(Color4f::from(style.color), &ColorSpace::new_srgb()); 23 | paint 24 | .set_anti_alias(true) 25 | .set_mask_filter(mask_filter.unwrap()); 26 | 27 | let mut clear_paint = Paint::new(Color4f::new(0.0, 0.0, 0.0, 0.0), &ColorSpace::new_srgb()); 28 | clear_paint 29 | .set_anti_alias(true) 30 | .set_blend_mode(BlendMode::Clear); 31 | 32 | canvas.save_layer(&SaveLayerRec::default()); 33 | canvas.draw_rrect(rrect, &paint); 34 | canvas.draw_rrect(rrect, &clear_paint); 35 | canvas.restore(); 36 | } 37 | -------------------------------------------------------------------------------- /irisia-widgets/src/box_styles/box_style_renderer.rs: -------------------------------------------------------------------------------- 1 | use irisia::{ 2 | primitive::{Point, Region}, 3 | read_style, 4 | skia_safe::{Canvas, Rect}, 5 | style::StyleContainer, 6 | }; 7 | 8 | use crate::box_styles::{ 9 | border::{draw_border, StyleBorder}, 10 | border_radius::{parse_border_radius, StyleBorderRadius}, 11 | box_shadow::draw_shadow, 12 | }; 13 | 14 | use super::{box_shadow::StyleBoxShadow, margin::StyleMargin}; 15 | 16 | pub struct BoxStyleRenderer; 17 | 18 | #[derive(Default)] 19 | struct BoundReduction { 20 | top: f32, 21 | right: f32, 22 | bottom: f32, 23 | left: f32, 24 | } 25 | 26 | impl BoxStyleRenderer { 27 | pub fn draw_border_limited( 28 | styles: &impl StyleContainer, 29 | canvas: &mut Canvas, 30 | maximum_region: Region, 31 | ) -> Region { 32 | let BoundReduction { 33 | top, 34 | right, 35 | bottom, 36 | left, 37 | } = Self::render(styles, canvas, maximum_region); 38 | ( 39 | maximum_region.0 + Point(left.into(), top.into()), 40 | maximum_region.1 - Point(right.into(), bottom.into()), 41 | ) 42 | } 43 | 44 | pub fn draw_border_unlimited( 45 | styles: &impl StyleContainer, 46 | canvas: &mut Canvas, 47 | content_region: Region, 48 | ) -> Region { 49 | let BoundReduction { 50 | top, 51 | right, 52 | bottom, 53 | left, 54 | } = Self::render(styles, canvas, content_region); 55 | ( 56 | content_region.0 - Point(left.into(), top.into()), 57 | content_region.1 + Point(right.into(), bottom.into()), 58 | ) 59 | } 60 | 61 | fn render(styles: &impl StyleContainer, canvas: &mut Canvas, region: Region) -> BoundReduction { 62 | read_style!(styles in styles => { 63 | border: Option, 64 | radius: StyleBorderRadius, 65 | box_shadow: Option, 66 | margin: StyleMargin, 67 | }); 68 | 69 | let mut reduction = BoundReduction::default(); 70 | 71 | let rect = { 72 | let StyleMargin { 73 | top, 74 | right, 75 | bottom, 76 | left, 77 | } = styles.margin; 78 | 79 | let (left, top, right, bottom) = ( 80 | left.to_physical(), 81 | top.to_physical(), 82 | right.to_physical(), 83 | bottom.to_physical(), 84 | ); 85 | 86 | reduction.left += left; 87 | reduction.top += top; 88 | reduction.right += right; 89 | reduction.bottom += bottom; 90 | 91 | Rect::new( 92 | region.0 .0.to_physical() + left, 93 | region.0 .1.to_physical() + top, 94 | region.1 .0.to_physical() - right, 95 | region.1 .1.to_physical() - bottom, 96 | ) 97 | }; 98 | 99 | let rrect = parse_border_radius(&rect, &styles.radius); 100 | 101 | if let Some(bs) = &styles.box_shadow { 102 | draw_shadow(canvas, &rrect, bs); 103 | } 104 | 105 | if let Some(border) = &styles.border { 106 | let width = draw_border(canvas, rrect, border); 107 | reduction.left += width; 108 | reduction.top += width; 109 | reduction.right += width; 110 | reduction.bottom += width; 111 | } 112 | 113 | reduction 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /irisia-widgets/src/box_styles/margin.rs: -------------------------------------------------------------------------------- 1 | use irisia::{primitive::Pixel, Style}; 2 | 3 | #[derive(Style, Clone, Copy)] 4 | #[style(from, from = "", impl_default)] 5 | pub struct StyleMargin { 6 | #[style(option, default)] 7 | pub left: Pixel, 8 | 9 | #[style(option, default)] 10 | pub top: Pixel, 11 | 12 | #[style(option, default)] 13 | pub right: Pixel, 14 | 15 | #[style(option, default)] 16 | pub bottom: Pixel, 17 | } 18 | 19 | impl From<(Pixel,)> for StyleMargin { 20 | fn from((px,): (Pixel,)) -> Self { 21 | Self { 22 | left: px, 23 | top: px, 24 | right: px, 25 | bottom: px, 26 | } 27 | } 28 | } 29 | 30 | impl From<(Pixel, Pixel)> for StyleMargin { 31 | fn from((x, y): (Pixel, Pixel)) -> Self { 32 | Self { 33 | left: x, 34 | top: y, 35 | right: x, 36 | bottom: y, 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /irisia-widgets/src/box_styles/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod border; 2 | pub mod border_radius; 3 | pub mod box_shadow; 4 | pub mod box_style_renderer; 5 | pub mod margin; 6 | 7 | pub use self::{ 8 | border::{DashStyle, StyleBorder}, 9 | border_radius::StyleBorderRadius, 10 | box_shadow::StyleBoxShadow, 11 | box_style_renderer::BoxStyleRenderer, 12 | margin::StyleMargin, 13 | }; 14 | -------------------------------------------------------------------------------- /irisia-widgets/src/box_styles/padding.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fancyflame/irisia-gui/9f98a6ce5f14ae1ae18295bb041000c370b43c35/irisia-widgets/src/box_styles/padding.rs -------------------------------------------------------------------------------- /irisia-widgets/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod box_styles; 2 | pub mod textbox; 3 | 4 | pub use irisia::*; 5 | -------------------------------------------------------------------------------- /irisia-widgets/src/textbox/styles.rs: -------------------------------------------------------------------------------- 1 | use irisia::{ 2 | primitive::Pixel, 3 | skia_safe::font_style::{Slant, Weight}, 4 | Style, 5 | }; 6 | 7 | #[derive(Style, Clone, Copy, PartialEq)] 8 | #[style(from, impl_default)] 9 | pub struct StyleFontSize(#[style(default = "Pixel(40.0)")] pub Pixel); 10 | 11 | #[derive(Style, Clone, PartialEq)] 12 | #[style(from = "", impl_default)] 13 | pub struct StyleFontSlant(#[style(default = "Slant::Upright")] pub Slant); 14 | 15 | impl StyleFontSlant { 16 | pub fn normal(&mut self) { 17 | self.0 = Slant::Upright 18 | } 19 | 20 | pub fn italic(&mut self) { 21 | self.0 = Slant::Italic 22 | } 23 | 24 | pub fn oblique(&mut self) { 25 | self.0 = Slant::Oblique 26 | } 27 | } 28 | 29 | #[derive(Style, Clone, Copy, PartialEq)] 30 | #[style(from = "[0]", impl_default)] 31 | pub struct StyleFontWeight(#[style(default = "Weight::NORMAL")] pub Weight); 32 | 33 | impl StyleFontWeight { 34 | pub fn exlight(&mut self) { 35 | self.0 = Weight::EXTRA_LIGHT; 36 | } 37 | 38 | pub fn light(&mut self) { 39 | self.0 = Weight::LIGHT; 40 | } 41 | 42 | pub fn normal(&mut self) { 43 | self.0 = Weight::NORMAL; 44 | } 45 | 46 | pub fn bold(&mut self) { 47 | self.0 = Weight::BOLD; 48 | } 49 | 50 | pub fn exbold(&mut self) { 51 | self.0 = Weight::EXTRA_BOLD; 52 | } 53 | } 54 | 55 | impl From for StyleFontWeight { 56 | fn from(value: u32) -> Self { 57 | StyleFontWeight(Weight::from(value as i32)) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /irisia-widgets/src/textbox_legacy/styles.rs: -------------------------------------------------------------------------------- 1 | use irisia_core::{ 2 | primitive::Pixel, 3 | skia_safe::font_style::{Slant, Weight}, 4 | Style, 5 | }; 6 | 7 | #[derive(Style, Clone, Copy, PartialEq)] 8 | #[style(from, impl_default)] 9 | pub struct StyleFontSize(#[style(default = "Pixel(40.0)")] pub Pixel); 10 | 11 | #[derive(Style, Clone, PartialEq)] 12 | #[style(from = "", impl_default)] 13 | pub struct StyleFontSlant(#[style(default = "Slant::Upright")] pub Slant); 14 | 15 | impl StyleFontSlant { 16 | pub fn normal(&mut self) { 17 | self.0 = Slant::Upright 18 | } 19 | 20 | pub fn italic(&mut self) { 21 | self.0 = Slant::Italic 22 | } 23 | 24 | pub fn oblique(&mut self) { 25 | self.0 = Slant::Oblique 26 | } 27 | } 28 | 29 | #[derive(Style, Clone, Copy, PartialEq)] 30 | #[style(from = "[0]", impl_default)] 31 | pub struct StyleFontWeight(#[style(default = "Weight::NORMAL")] pub Weight); 32 | 33 | impl StyleFontWeight { 34 | pub fn exlight(&mut self) { 35 | self.0 = Weight::EXTRA_LIGHT; 36 | } 37 | 38 | pub fn light(&mut self) { 39 | self.0 = Weight::LIGHT; 40 | } 41 | 42 | pub fn normal(&mut self) { 43 | self.0 = Weight::NORMAL; 44 | } 45 | 46 | pub fn bold(&mut self) { 47 | self.0 = Weight::BOLD; 48 | } 49 | 50 | pub fn exbold(&mut self) { 51 | self.0 = Weight::EXTRA_BOLD; 52 | } 53 | } 54 | 55 | impl From for StyleFontWeight { 56 | fn from(value: u32) -> Self { 57 | StyleFontWeight(Weight::from(value as i32)) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /irisia/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "irisia" 3 | version = "1.0.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1" 10 | take_mut = "0.2" 11 | smallvec = "1.9" 12 | async-trait = "0.1" 13 | tokio = { version = "1.32", features = [ 14 | "sync", 15 | "time", 16 | "macros", 17 | "rt-multi-thread", 18 | ] } 19 | irisia_macros = { path = "../irisia-macros" } 20 | irisia_backend = { path = "../irisia-backend", features = ["fps_recorder"] } 21 | irisia_utils = { path = "../irisia-utils" } 22 | -------------------------------------------------------------------------------- /irisia/src/application/backend.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc, sync::Arc, time::Duration}; 2 | 3 | use irisia_backend::{ 4 | skia_safe::{colors::WHITE, Canvas}, 5 | window_handle::{RawWindowHandle, WindowBuilder}, 6 | winit::dpi::PhysicalSize, 7 | AppWindow, StaticWindowEvent, WinitWindow, 8 | }; 9 | 10 | use crate::{ 11 | dom::{one_child, update::ElementModelUpdater, DropProtection, EMUpdateContent}, 12 | element::{Element, ElementUpdate}, 13 | event::{standard::WindowDestroyed, EventDispatcher}, 14 | primitive::{Pixel, Point, Region}, 15 | update_with::UpdateWith, 16 | Result, 17 | }; 18 | 19 | use super::{ 20 | content::GlobalContent, 21 | event_comp::{global::focusing::Focusing, GlobalEventMgr}, 22 | redraw_scheduler::RedrawScheduler, 23 | Window, 24 | }; 25 | 26 | pub(super) struct BackendRuntime { 27 | gem: GlobalEventMgr, 28 | gc: Rc, 29 | root_element: DropProtection, 30 | } 31 | 32 | impl AppWindow for BackendRuntime 33 | where 34 | El: Element, 35 | { 36 | fn on_redraw(&mut self, canvas: &mut Canvas, interval: Duration) -> Result<()> { 37 | self.gc 38 | .redraw_scheduler 39 | .borrow_mut() 40 | .redraw(canvas, interval)?; 41 | 42 | // composite 43 | canvas.reset_matrix(); 44 | canvas.clear(WHITE); 45 | self.root_element.composite(canvas) 46 | } 47 | 48 | fn on_window_event(&mut self, event: StaticWindowEvent) { 49 | if let StaticWindowEvent::Resized(size) = &event { 50 | self.root_element 51 | .set_draw_region(window_size_to_draw_region(*size)); 52 | } 53 | 54 | if let Some(npe) = self.gem.emit_event(event, &self.gc) { 55 | if !self.root_element.emit_event(&npe) { 56 | npe.focus_on(None); 57 | } 58 | } 59 | } 60 | 61 | fn on_destroy(&mut self) { 62 | self.gc.event_dispatcher().emit_trusted(WindowDestroyed); 63 | } 64 | } 65 | 66 | fn window_size_to_draw_region(size: PhysicalSize) -> Region { 67 | ( 68 | Point(Pixel(0.0), Pixel(0.0)), 69 | Point( 70 | Pixel::from_physical(size.width as _), 71 | Pixel::from_physical(size.height as _), 72 | ), 73 | ) 74 | } 75 | 76 | pub(super) async fn new_window(window_builder: F) -> Result 77 | where 78 | El: Element + ElementUpdate<()>, 79 | F: FnOnce(WindowBuilder) -> WindowBuilder + Send + 'static, 80 | { 81 | let ev_disp = EventDispatcher::new(); 82 | 83 | let create_app = { 84 | let ev_disp = ev_disp.clone(); 85 | 86 | move |window: Arc, close_handle| { 87 | let redraw_scheduler = RedrawScheduler::new(window.clone()); 88 | 89 | let gc = Rc::new(GlobalContent { 90 | global_ed: ev_disp, 91 | focusing: Focusing::new(), 92 | window, 93 | redraw_scheduler: RefCell::new(redraw_scheduler), 94 | close_handle, 95 | }); 96 | 97 | let root_element = as UpdateWith< 98 | ElementModelUpdater<'_, El, (), (), (), _>, 99 | >>::create_with(ElementModelUpdater { 100 | add_one: one_child((), (), (), |_: &_| {}), 101 | content: EMUpdateContent { 102 | global_content: &gc, 103 | parent_layer: None, 104 | }, 105 | }); 106 | 107 | root_element.set_draw_region(window_size_to_draw_region(gc.window().inner_size())); 108 | gc.request_redraw(root_element.0.clone()); 109 | 110 | BackendRuntime:: { 111 | root_element, 112 | gem: GlobalEventMgr::new(), 113 | gc, 114 | } 115 | } 116 | }; 117 | 118 | let RawWindowHandle { 119 | raw_window, 120 | close_handle, 121 | } = RawWindowHandle::create(create_app, window_builder).await?; 122 | 123 | Ok(Window { 124 | winit_window: Arc::downgrade(&raw_window), 125 | close_handle, 126 | event_dispatcher: ev_disp, 127 | }) 128 | } 129 | -------------------------------------------------------------------------------- /irisia/src/application/content.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc, sync::Arc}; 2 | 3 | use irisia_backend::{window_handle::CloseHandle, WinitWindow}; 4 | 5 | use crate::event::EventDispatcher; 6 | 7 | use super::{ 8 | event_comp::global::focusing::Focusing, 9 | redraw_scheduler::{RedrawObject, RedrawScheduler}, 10 | }; 11 | 12 | pub struct GlobalContent { 13 | pub(super) focusing: Focusing, 14 | pub(super) global_ed: EventDispatcher, 15 | pub(super) window: Arc, 16 | pub(super) close_handle: CloseHandle, 17 | pub(super) redraw_scheduler: RefCell, 18 | } 19 | 20 | impl GlobalContent { 21 | pub fn blur(&self) { 22 | self.focusing.blur(); 23 | } 24 | 25 | pub(crate) fn focusing(&self) -> &Focusing { 26 | &self.focusing 27 | } 28 | 29 | pub(crate) fn request_redraw(&self, ro: Rc) { 30 | self.redraw_scheduler.borrow_mut().request_redraw(ro) 31 | } 32 | 33 | /// Returns a reference to the global event dispatcher 34 | pub fn event_dispatcher(&self) -> &EventDispatcher { 35 | &self.global_ed 36 | } 37 | 38 | /// Returns the close handle 39 | pub fn close_handle(&self) -> CloseHandle { 40 | self.close_handle 41 | } 42 | 43 | /// Closes the window 44 | pub fn close_window(&self) { 45 | self.close_handle.close(); 46 | } 47 | 48 | /// Returns a reference to the window 49 | pub fn window(&self) -> &WinitWindow { 50 | &self.window 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /irisia/src/application/event_comp/global/focusing.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex as StdMutex}; 2 | 3 | use crate::event::{ 4 | standard::{Blured, Focused}, 5 | EventDispatcher, 6 | }; 7 | 8 | pub struct Focusing(Arc>>); 9 | 10 | impl Focusing { 11 | pub fn new() -> Self { 12 | Focusing(Default::default()) 13 | } 14 | 15 | pub fn focus(&self, ed: EventDispatcher) { 16 | let mut guard = self.0.lock().unwrap(); 17 | 18 | match &*guard { 19 | Some(old_ed) if ed.ptr_eq(old_ed) => {} 20 | _ => { 21 | blur(&mut guard); 22 | ed.emit_trusted(Focused); 23 | *guard = Some(ed); 24 | } 25 | } 26 | } 27 | 28 | pub fn blur(&self) { 29 | blur(&mut self.0.lock().unwrap()) 30 | } 31 | 32 | pub fn blur_checked(&self, ed: &EventDispatcher) { 33 | let mut guard = self.0.lock().unwrap(); 34 | if let Some(focused) = &mut *guard { 35 | if focused.is_same(ed) { 36 | blur(&mut guard); 37 | } 38 | } 39 | } 40 | } 41 | 42 | fn blur(ed: &mut Option) { 43 | if let Some(ed) = ed.take() { 44 | ed.emit_trusted(Blured); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /irisia/src/application/event_comp/global/new_event.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | 3 | use irisia_backend::StaticWindowEvent; 4 | 5 | use crate::{ 6 | application::content::GlobalContent, 7 | event::EventDispatcher, 8 | primitive::{Pixel, Point}, 9 | }; 10 | 11 | use super::{GlobalEventMgr, PointerState}; 12 | 13 | pub struct NewPointerEvent<'a> { 14 | pub(crate) event: StaticWindowEvent, 15 | pub(crate) gem: &'a mut GlobalEventMgr, 16 | pub(crate) global_content: &'a GlobalContent, 17 | pub(crate) new_position: Option, 18 | pub(crate) cursor_delta: Option<(Pixel, Pixel)>, 19 | new_focused: Cell, 20 | pub(crate) new_pointer_state: PointerState, 21 | pub(crate) pointer_state_change: PointerStateChange, 22 | } 23 | 24 | enum NewFocused { 25 | Unchanged, 26 | ChangeTo(EventDispatcher), 27 | Blur, 28 | } 29 | 30 | #[derive(Clone, Copy, Debug)] 31 | pub(crate) enum PointerStateChange { 32 | Unchange, 33 | Press, 34 | Release, 35 | LeaveViewport, 36 | EnterViewport, 37 | } 38 | 39 | impl<'a> NewPointerEvent<'a> { 40 | pub(super) fn new( 41 | event: StaticWindowEvent, 42 | gem: &'a mut GlobalEventMgr, 43 | gc: &'a GlobalContent, 44 | new_position: Option, 45 | new_pointer_state: PointerState, 46 | ) -> Self { 47 | let cursor_delta = gem 48 | .last_cursor_position 49 | .zip(new_position) 50 | .map(|(old, new)| (new.0 - old.0, new.1 - old.1)); 51 | 52 | NewPointerEvent { 53 | event, 54 | new_position, 55 | cursor_delta, 56 | new_focused: Cell::new(NewFocused::Unchanged), 57 | new_pointer_state, 58 | pointer_state_change: PointerStateChange::difference_between( 59 | gem.pointer_state, 60 | new_pointer_state, 61 | ), 62 | gem, 63 | global_content: gc, 64 | } 65 | } 66 | 67 | pub(crate) fn focus_on(&self, ed: Option) { 68 | self.new_focused.set(match ed { 69 | Some(ed) => NewFocused::ChangeTo(ed), 70 | None => NewFocused::Blur, 71 | }); 72 | } 73 | } 74 | 75 | impl PointerStateChange { 76 | fn difference_between(old: PointerState, new: PointerState) -> Self { 77 | use PointerState::*; 78 | 79 | match (old, new) { 80 | (Release, Pressing) => Self::Press, 81 | (Pressing, Release) => Self::Release, 82 | (OutOfViewport, Pressing | Release) => Self::EnterViewport, 83 | (Pressing | Release, OutOfViewport) => Self::LeaveViewport, 84 | (Release, Release) | (Pressing, Pressing) | (OutOfViewport, OutOfViewport) => { 85 | Self::Unchange 86 | } 87 | } 88 | } 89 | } 90 | 91 | impl Drop for NewPointerEvent<'_> { 92 | fn drop(&mut self) { 93 | self.gem.last_cursor_position = self.new_position; 94 | 95 | match self.new_focused.replace(NewFocused::Unchanged) { 96 | NewFocused::Unchanged => (), 97 | NewFocused::ChangeTo(ed) => self.global_content.focusing.focus(ed), 98 | NewFocused::Blur => self.global_content.focusing.blur(), 99 | } 100 | 101 | self.gem.pointer_state = self.new_pointer_state; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /irisia/src/application/event_comp/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod global; 2 | pub mod node; 3 | 4 | pub(crate) use self::{ 5 | global::{new_event::NewPointerEvent, GlobalEventMgr}, 6 | node::NodeEventMgr, 7 | }; 8 | -------------------------------------------------------------------------------- /irisia/src/application/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Weak; 2 | 3 | use irisia_backend::{window_handle::WindowBuilder, WinitWindow}; 4 | 5 | use crate::{ 6 | element::{Element, ElementUpdate}, 7 | event::{standard::WindowDestroyed, EventDispatcher}, 8 | Result, 9 | }; 10 | 11 | mod backend; 12 | pub(crate) mod content; 13 | pub(crate) mod event_comp; 14 | pub(crate) mod redraw_scheduler; 15 | 16 | use backend::new_window; 17 | 18 | pub use irisia_backend::window_handle::CloseHandle; 19 | 20 | #[derive(Clone)] 21 | pub struct Window { 22 | winit_window: Weak, 23 | close_handle: CloseHandle, 24 | event_dispatcher: EventDispatcher, 25 | } 26 | 27 | impl Window { 28 | pub async fn new(title: impl Into) -> Result 29 | where 30 | El: Element + ElementUpdate<()>, 31 | { 32 | let title = title.into(); 33 | new_window::(move |wb| wb.with_title(title)).await 34 | } 35 | 36 | pub async fn with_builder(f: F) -> Result 37 | where 38 | El: Element + ElementUpdate<()>, 39 | F: FnOnce(WindowBuilder) -> WindowBuilder + Send + 'static, 40 | { 41 | new_window::(f).await 42 | } 43 | 44 | pub fn winit_window(&self) -> &Weak { 45 | &self.winit_window 46 | } 47 | 48 | pub fn close_handle(&self) -> CloseHandle { 49 | self.close_handle 50 | } 51 | 52 | pub fn close(&self) { 53 | self.close_handle.close(); 54 | } 55 | 56 | pub fn event_dispatcher(&self) -> &EventDispatcher { 57 | &self.event_dispatcher 58 | } 59 | 60 | pub async fn join(&self) { 61 | if self.winit_window.strong_count() == 0 { 62 | return; 63 | } 64 | 65 | self.event_dispatcher 66 | .recv_trusted::() 67 | .await; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /irisia/src/application/redraw_scheduler.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, rc::Rc, sync::Arc, time::Duration}; 2 | 3 | use anyhow::anyhow; 4 | use irisia_backend::{ 5 | skia_safe::{colors::TRANSPARENT, Canvas}, 6 | WinitWindow, 7 | }; 8 | 9 | use crate::Result; 10 | 11 | pub(super) struct RedrawScheduler { 12 | window: Arc, 13 | list: HashMap<*const dyn RedrawObject, Rc>, 14 | redraw_req_sent: bool, 15 | } 16 | 17 | impl RedrawScheduler { 18 | pub fn new(window: Arc) -> Self { 19 | Self { 20 | window, 21 | list: HashMap::new(), 22 | redraw_req_sent: false, 23 | } 24 | } 25 | 26 | pub fn request_redraw(&mut self, ro: Rc) { 27 | if !self.redraw_req_sent { 28 | self.redraw_req_sent = true; 29 | self.window.request_redraw(); 30 | } 31 | self.list.insert(Rc::as_ptr(&ro), ro); 32 | } 33 | 34 | pub fn redraw(&mut self, canvas: &mut Canvas, interval: Duration) -> Result<()> { 35 | let mut errors = Vec::new(); 36 | self.redraw_req_sent = false; 37 | 38 | for (_, ro) in self.list.drain() { 39 | canvas.clear(TRANSPARENT); 40 | canvas.reset_matrix(); 41 | 42 | if let Err(err) = ro.redraw(canvas, interval) { 43 | errors.push(err); 44 | } 45 | } 46 | 47 | fmt_errors(&errors) 48 | } 49 | } 50 | 51 | fn fmt_errors(errors: &[anyhow::Error]) -> Result<()> { 52 | if errors.is_empty() { 53 | return Ok(()); 54 | } 55 | 56 | let mut msg = String::new(); 57 | for (index, err) in errors.iter().enumerate() { 58 | msg += &format!("`{err}`"); 59 | if index != errors.len() - 1 { 60 | msg += ", "; 61 | } 62 | } 63 | 64 | Err(anyhow!( 65 | "{} error(s) occurred on redraw: {}", 66 | errors.len(), 67 | msg 68 | )) 69 | } 70 | 71 | pub(crate) trait RedrawObject { 72 | fn redraw(&self, canvas: &mut Canvas, interval: Duration) -> Result<()>; 73 | } 74 | -------------------------------------------------------------------------------- /irisia/src/dom/children/children_node.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | dom::update::EMUpdateContent, structure::MapVisit, update_with::SpecificUpdate, UpdateWith, 3 | }; 4 | 5 | use super::RenderMultiple; 6 | 7 | pub trait ChildrenNodes 8 | where 9 | Self: for<'a> Helper<'a, HelperModel = Self::Model>, 10 | { 11 | type Model: RenderMultiple; 12 | } 13 | 14 | impl ChildrenNodes for T 15 | where 16 | T: for<'a> Helper<'a, HelperModel = M>, 17 | M: RenderMultiple, 18 | { 19 | type Model = M; 20 | } 21 | 22 | pub trait Helper<'a>: Sized { 23 | type HelperModel: RenderMultiple; 24 | 25 | fn create_model(self, updater: EMUpdateContent<'a>) -> Self::HelperModel; 26 | fn update_model( 27 | self, 28 | model: &mut Self::HelperModel, 29 | updater: EMUpdateContent<'a>, 30 | equality_matters: &mut bool, 31 | ); 32 | } 33 | 34 | impl<'a, T, M> Helper<'a> for T 35 | where 36 | T: MapVisit>, 37 | T::Output: SpecificUpdate, 38 | M: RenderMultiple + UpdateWith + 'static, 39 | { 40 | type HelperModel = M; 41 | 42 | fn create_model(self, updater: EMUpdateContent<'a>) -> Self::HelperModel { 43 | M::create_with(self.map(&updater)) 44 | } 45 | 46 | fn update_model( 47 | self, 48 | model: &mut Self::HelperModel, 49 | updater: EMUpdateContent<'a>, 50 | equality_matters: &mut bool, 51 | ) { 52 | *equality_matters &= model.update_with(self.map(&updater), *equality_matters); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /irisia/src/dom/children/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) use self::{children_node::ChildrenNodes, render_multiple::RenderMultiple}; 2 | 3 | mod children_node; 4 | mod render_multiple; 5 | 6 | pub(crate) struct ChildrenBox { 7 | structure: Box, 8 | } 9 | 10 | impl ChildrenBox { 11 | pub fn new(value: T) -> Self 12 | where 13 | T: RenderMultiple, 14 | { 15 | ChildrenBox { 16 | structure: Box::new(value), 17 | } 18 | } 19 | 20 | pub fn as_render_multiple(&mut self) -> &mut dyn RenderMultiple { 21 | &mut *self.structure 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /irisia/src/dom/children/render_multiple.rs: -------------------------------------------------------------------------------- 1 | use std::{any::Any, time::Duration}; 2 | 3 | use anyhow::anyhow; 4 | 5 | use crate::{ 6 | application::event_comp::NewPointerEvent, 7 | dom::{layer::LayerRebuilder, DropProtection}, 8 | element::Element, 9 | primitive::Region, 10 | structure::{Visit, VisitLen, Visitor}, 11 | style::{style_box::InsideStyleBox, StyleContainer}, 12 | Result, 13 | }; 14 | 15 | pub trait RenderMultiple: 'static { 16 | fn render(&self, lr: &mut LayerRebuilder, interval: Duration) -> Result<()>; 17 | 18 | fn peek_styles(&self, f: &mut dyn FnMut(&dyn InsideStyleBox)); 19 | 20 | fn len(&self) -> usize; 21 | 22 | fn layout(&self, f: &mut dyn FnMut(&dyn InsideStyleBox) -> Option) -> Result<()>; 23 | 24 | fn emit_event(&self, npe: &NewPointerEvent) -> bool; 25 | 26 | fn as_any(&mut self) -> &mut dyn Any; 27 | } 28 | 29 | impl RenderMultiple for T 30 | where 31 | T: for<'a, 'lr> Visit> 32 | + for<'a, 'root> Visit> 33 | + for<'a> Visit> 34 | + for<'a> Visit> 35 | + 'static, 36 | { 37 | fn render(&self, lr: &mut LayerRebuilder, interval: Duration) -> Result<()> { 38 | self.visit(&mut RenderHelper { lr, interval }) 39 | } 40 | 41 | fn peek_styles(&self, f: &mut dyn FnMut(&dyn InsideStyleBox)) { 42 | let _ = self.visit(&mut PeekStyles(f)); 43 | } 44 | 45 | fn len(&self) -> usize { 46 | VisitLen::len(self) 47 | } 48 | 49 | fn layout(&self, f: &mut dyn FnMut(&dyn InsideStyleBox) -> Option) -> Result<()> { 50 | self.visit(&mut LayoutHelper(f)) 51 | } 52 | 53 | fn emit_event(&self, npe: &NewPointerEvent) -> bool { 54 | let mut logical_entered = false; 55 | let mut eeh = EmitEventHelper { 56 | children_entered: &mut logical_entered, 57 | npe, 58 | }; 59 | let _ = self.visit(&mut eeh); 60 | logical_entered 61 | } 62 | 63 | fn as_any(&mut self) -> &mut dyn Any { 64 | self 65 | } 66 | } 67 | 68 | struct RenderHelper<'a, 'lr> { 69 | lr: &'a mut LayerRebuilder<'lr>, 70 | interval: Duration, 71 | } 72 | 73 | impl Visitor> for RenderHelper<'_, '_> 74 | where 75 | El: Element, 76 | Sty: StyleContainer, 77 | Sc: RenderMultiple, 78 | { 79 | fn visit(&mut self, data: &DropProtection) -> Result<()> { 80 | data.build_layers(self.lr, self.interval) 81 | } 82 | } 83 | 84 | struct LayoutHelper<'a>(&'a mut dyn FnMut(&dyn InsideStyleBox) -> Option); 85 | 86 | impl Visitor> for LayoutHelper<'_> 87 | where 88 | El: Element, 89 | Sty: StyleContainer, 90 | Sc: RenderMultiple, 91 | { 92 | fn visit(&mut self, data: &DropProtection) -> Result<()> { 93 | let region = (self.0)(&data.in_cell.borrow().styles); 94 | match region { 95 | Some(region) => { 96 | data.set_draw_region(region); 97 | Ok(()) 98 | } 99 | None => Err(anyhow!("unexpected end of layouter")), 100 | } 101 | } 102 | } 103 | 104 | struct PeekStyles<'a>(&'a mut dyn FnMut(&dyn InsideStyleBox)); 105 | 106 | impl Visitor> for PeekStyles<'_> 107 | where 108 | El: Element, 109 | Sty: StyleContainer, 110 | Sc: RenderMultiple, 111 | { 112 | fn visit(&mut self, data: &DropProtection) -> Result<()> { 113 | (self.0)(&data.in_cell.borrow().styles); 114 | Ok(()) 115 | } 116 | } 117 | 118 | struct EmitEventHelper<'a, 'root> { 119 | npe: &'a NewPointerEvent<'root>, 120 | children_entered: &'a mut bool, 121 | } 122 | 123 | impl Visitor> for EmitEventHelper<'_, '_> 124 | where 125 | El: Element, 126 | Sty: StyleContainer, 127 | Sc: RenderMultiple, 128 | { 129 | fn visit(&mut self, data: &DropProtection) -> Result<()> { 130 | *self.children_entered |= data.emit_event(self.npe); 131 | Ok(()) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /irisia/src/dom/data_structure.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::{Cell, RefCell}, 3 | rc::{Rc, Weak}, 4 | }; 5 | 6 | use tokio::sync::RwLock; 7 | 8 | use crate::{ 9 | application::{ 10 | content::GlobalContent, event_comp::NodeEventMgr, redraw_scheduler::RedrawObject, 11 | }, 12 | event::EventDispatcher, 13 | primitive::Region, 14 | structure::slot::Slot, 15 | style::StyleContainer, 16 | Element, 17 | }; 18 | 19 | use super::{children::ChildrenBox, layer::SharedLayerCompositer, RenderMultiple}; 20 | 21 | pub struct ElementModel 22 | where 23 | El: Element, 24 | Sty: StyleContainer, 25 | Sc: RenderMultiple, 26 | { 27 | pub(super) this: Weak, 28 | pub(super) el: RwLock>, 29 | pub(super) el_alive: Cell, 30 | pub(super) global_content: Rc, 31 | pub(super) ed: EventDispatcher, 32 | pub(super) slot_cache: Slot, 33 | pub(super) draw_region: Cell, 34 | pub(super) interact_region: Cell>, 35 | pub(super) acquire_independent_layer: Cell, 36 | pub(super) in_cell: RefCell>, 37 | } 38 | 39 | pub(super) struct InsideRefCell { 40 | pub styles: Sty, 41 | pub expanded_children: Option, 42 | pub event_mgr: NodeEventMgr, 43 | pub parent_layer: Option>, 44 | pub indep_layer: Option, 45 | } 46 | -------------------------------------------------------------------------------- /irisia/src/dom/drop_protection.rs: -------------------------------------------------------------------------------- 1 | use crate::{style::StyleContainer, Element}; 2 | 3 | use super::{RcElementModel, RenderMultiple}; 4 | 5 | use std::ops::Deref; 6 | 7 | pub struct DropProtection(pub RcElementModel) 8 | where 9 | El: Element, 10 | Sty: StyleContainer + 'static, 11 | Sc: RenderMultiple + 'static; 12 | 13 | impl Deref for DropProtection 14 | where 15 | El: Element, 16 | Sty: StyleContainer + 'static, 17 | Sc: RenderMultiple + 'static, 18 | { 19 | type Target = RcElementModel; 20 | fn deref(&self) -> &Self::Target { 21 | &self.0 22 | } 23 | } 24 | 25 | impl Drop for DropProtection 26 | where 27 | El: Element, 28 | Sty: StyleContainer + 'static, 29 | Sc: RenderMultiple + 'static, 30 | { 31 | fn drop(&mut self) { 32 | self.0.set_abandoned(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /irisia/src/dom/layer/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use anyhow::anyhow; 4 | use irisia_backend::skia_safe::{ 5 | canvas::{SaveLayerFlags, SaveLayerRec}, 6 | BlendMode, Canvas, Paint, 7 | }; 8 | 9 | use self::queue::{Layer, Queue}; 10 | use crate::Result; 11 | pub(crate) use rebuild::LayerRebuilder; 12 | 13 | mod queue; 14 | pub(crate) mod rebuild; 15 | 16 | pub(crate) type SharedLayerCompositer = Rc>; 17 | 18 | pub(crate) struct LayerCompositer { 19 | layers: Queue, 20 | } 21 | 22 | impl LayerCompositer { 23 | pub fn new() -> SharedLayerCompositer { 24 | Rc::new(RefCell::new(Self { 25 | layers: Queue::new(), 26 | })) 27 | } 28 | 29 | pub fn rebuild<'a>(&'a mut self, canvas: &'a mut Canvas) -> LayerRebuilder<'a> { 30 | self.layers.clear(); 31 | LayerRebuilder::new(self, canvas) 32 | } 33 | 34 | pub fn composite(&self, canvas: &mut Canvas) -> Result<()> { 35 | let mut paint = Paint::default(); 36 | paint.set_blend_mode(BlendMode::DstOver); 37 | let rec = SaveLayerRec::default() 38 | .flags(SaveLayerFlags::INIT_WITH_PREVIOUS) 39 | .paint(&paint); 40 | canvas.save_layer(&rec); 41 | 42 | for layer in self.layers.iter() { 43 | match layer { 44 | Layer::Normal(bitmap) => { 45 | if !canvas.write_pixels_from_bitmap(bitmap, (0, 0)) { 46 | return Err(anyhow!("cannot write bitmap to canvas")); 47 | } 48 | } 49 | Layer::Extern { layer, matrix } => { 50 | canvas.save(); 51 | canvas.set_matrix(matrix); 52 | layer.borrow().composite(canvas)?; 53 | canvas.restore(); 54 | } 55 | } 56 | } 57 | 58 | canvas.restore(); 59 | Ok(()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /irisia/src/dom/layer/queue.rs: -------------------------------------------------------------------------------- 1 | use irisia_backend::skia_safe::{Bitmap, ImageInfo, M44}; 2 | use smallvec::SmallVec; 3 | 4 | use super::SharedLayerCompositer; 5 | 6 | pub(super) enum Layer { 7 | Normal(Bitmap), 8 | Extern { 9 | layer: SharedLayerCompositer, 10 | matrix: M44, 11 | }, 12 | } 13 | 14 | pub(super) struct Queue { 15 | buffer: SmallVec<[Layer; 1]>, 16 | len: usize, 17 | } 18 | 19 | impl Queue { 20 | pub fn new() -> Self { 21 | Self { 22 | buffer: SmallVec::new(), 23 | len: 0, 24 | } 25 | } 26 | 27 | pub fn clear(&mut self) { 28 | self.len = 0; 29 | } 30 | 31 | pub fn add_bitmap(&mut self, image_info: &ImageInfo) -> &mut Bitmap { 32 | loop { 33 | match self.buffer.get_mut(self.len) { 34 | None => { 35 | let mut bitmap = Bitmap::new(); 36 | assert!(bitmap.set_info(image_info, None)); 37 | self.buffer.push(Layer::Normal(bitmap)); 38 | break; 39 | } 40 | Some(Layer::Normal(bitmap)) => { 41 | assert!(bitmap.set_info(image_info, None)); 42 | break; 43 | } 44 | Some(Layer::Extern { .. }) => { 45 | self.buffer.swap_remove(self.len); 46 | } 47 | } 48 | } 49 | 50 | self.len += 1; 51 | match self.buffer.last_mut() { 52 | Some(Layer::Normal(bitmap)) => bitmap, 53 | _ => unreachable!(), 54 | } 55 | } 56 | 57 | pub fn add_layer(&mut self, layer: SharedLayerCompositer, matrix: M44) { 58 | let layer = Layer::Extern { layer, matrix }; 59 | 60 | match self.buffer.get_mut(self.len) { 61 | Some(ext @ Layer::Extern { .. }) => *ext = layer, 62 | Some(normal @ Layer::Normal { .. }) => { 63 | let bitmap = std::mem::replace(normal, layer); 64 | self.buffer.push(bitmap); 65 | } 66 | None => { 67 | self.buffer.push(layer); 68 | } 69 | } 70 | self.len += 1; 71 | } 72 | 73 | pub fn iter(&self) -> impl Iterator { 74 | self.buffer.iter().take(self.len) 75 | } 76 | 77 | pub fn pop(&mut self) { 78 | if let Some(len) = self.len.checked_sub(1) { 79 | self.len = len; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /irisia/src/dom/layer/rebuild.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use irisia_backend::skia_safe::{colors::TRANSPARENT, Canvas}; 3 | 4 | use super::{LayerCompositer, SharedLayerCompositer}; 5 | use crate::Result; 6 | 7 | pub struct LayerRebuilder<'a> { 8 | pub(super) lc: &'a mut LayerCompositer, 9 | pub(super) canvas: &'a mut Canvas, 10 | dirty: bool, 11 | } 12 | 13 | impl<'a> LayerRebuilder<'a> { 14 | pub(super) fn new(lc: &'a mut LayerCompositer, canvas: &'a mut Canvas) -> Self { 15 | canvas.save(); 16 | canvas.reset_matrix(); 17 | Self { 18 | lc, 19 | canvas, 20 | dirty: false, 21 | } 22 | } 23 | 24 | pub(crate) fn draw_in_place(&mut self) -> &mut Canvas { 25 | if self.dirty { 26 | self.canvas.restore(); 27 | } 28 | self.dirty = true; 29 | self.canvas.save(); 30 | self.canvas 31 | } 32 | 33 | pub(crate) fn new_layer(&mut self, custom_layer: SharedLayerCompositer) -> Result<()> { 34 | self.flush()?; 35 | let matrix = self.canvas.local_to_device(); 36 | self.lc.layers.add_layer(custom_layer, matrix); 37 | self.canvas.clear(TRANSPARENT); 38 | Ok(()) 39 | } 40 | 41 | fn flush(&mut self) -> Result<()> { 42 | if !self.dirty { 43 | return Ok(()); 44 | } 45 | 46 | let bitmap = self.lc.layers.add_bitmap(&self.canvas.image_info()); 47 | bitmap.alloc_pixels(); 48 | if !self.canvas.read_pixels_to_bitmap(bitmap, (0, 0)) { 49 | self.lc.layers.pop(); 50 | return Err(anyhow!("cannot flush canvas content")); 51 | } 52 | self.canvas.restore(); 53 | self.dirty = false; 54 | 55 | Ok(()) 56 | } 57 | } 58 | 59 | impl Drop for LayerRebuilder<'_> { 60 | fn drop(&mut self) { 61 | self.flush().expect("flush at drop time failed"); 62 | self.canvas.clear(TRANSPARENT); 63 | self.canvas.restore(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /irisia/src/dom/pub_handle/layout_el.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefMut; 2 | 3 | use crate::{ 4 | dom::children::RenderMultiple, primitive::Region, style::StyleContainer, Result, StyleReader, 5 | }; 6 | 7 | #[must_use] 8 | pub struct LayoutElements<'a> { 9 | pub(super) refmut: RefMut<'a, dyn RenderMultiple>, 10 | } 11 | 12 | impl<'a> LayoutElements<'a> { 13 | pub fn peek_styles(&self, mut f: F) 14 | where 15 | F: FnMut(Sr), 16 | Sr: StyleReader, 17 | { 18 | self.refmut 19 | .peek_styles(&mut |inside_style_box| f(inside_style_box.read())); 20 | } 21 | 22 | pub fn len(&self) -> usize { 23 | self.refmut.len() 24 | } 25 | 26 | pub fn is_empty(&self) -> bool { 27 | self.refmut.len() == 0 28 | } 29 | 30 | pub fn layout(self, mut layouter: F) -> Result<()> 31 | where 32 | F: FnMut(Sr) -> Option, 33 | Sr: StyleReader, 34 | { 35 | self.refmut 36 | .layout(&mut |inside_style_box| layouter(inside_style_box.read())) 37 | } 38 | 39 | pub fn layout_once(self, draw_region: Region) -> Result<()> { 40 | let mut dr = Some(draw_region); 41 | self.layout(|()| dr.take()) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /irisia/src/dom/pub_handle/write_guard.rs: -------------------------------------------------------------------------------- 1 | use tokio::sync::RwLockMappedWriteGuard; 2 | 3 | use std::ops::{Deref, DerefMut}; 4 | 5 | use crate::{dom::RenderMultiple, element::RcElementModel, style::StyleContainer, Element}; 6 | 7 | pub struct ElWriteGuard<'a, El, Sd: SetDirty> { 8 | pub(super) write: RwLockMappedWriteGuard<'a, El>, 9 | pub(super) set_dirty: &'a Sd, 10 | } 11 | 12 | impl> Deref for ElWriteGuard<'_, El, Sd> { 13 | type Target = El; 14 | fn deref(&self) -> &Self::Target { 15 | &self.write 16 | } 17 | } 18 | 19 | impl> DerefMut for ElWriteGuard<'_, El, Sd> { 20 | fn deref_mut(&mut self) -> &mut Self::Target { 21 | &mut self.write 22 | } 23 | } 24 | 25 | impl> Drop for ElWriteGuard<'_, El, Sd> { 26 | fn drop(&mut self) { 27 | self.set_dirty._set_dirty(&self.write); 28 | } 29 | } 30 | 31 | pub trait SetDirty { 32 | fn _set_dirty(&self, el: &El); 33 | } 34 | 35 | impl SetDirty for RcElementModel 36 | where 37 | El: Element, 38 | Sty: StyleContainer + 'static, 39 | Sc: RenderMultiple + 'static, 40 | { 41 | fn _set_dirty(&self, el: &El) { 42 | el.set_children(self); 43 | self.set_dirty(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /irisia/src/element/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{dom::RenderMultiple, primitive::Region, Result}; 2 | 3 | pub use self::{props::PropsUpdateWith, render_element::RenderElement}; 4 | pub use crate::{ 5 | application::content::GlobalContent, 6 | dom::{one_child, pub_handle::LayoutElements, RcElementModel}, 7 | }; 8 | 9 | pub mod props; 10 | mod render_element; 11 | 12 | #[macro_export] 13 | macro_rules! ElModel { 14 | () => { 15 | $crate::ElModel!(Self) 16 | }; 17 | ($El: ty) => { 18 | $crate::element::RcElementModel< 19 | $El, 20 | impl $crate::style::StyleContainer + 'static, 21 | impl $crate::element::AsChildren + 'static 22 | > 23 | }; 24 | } 25 | 26 | /// Element is a thing can draw itself on the given canvas, 27 | /// according to its properties, styles and given drawing region. 28 | /// This trait is close to the native rendering, if you are not a 29 | /// component maker, please using exist elements or macros to 30 | /// customize one. 31 | pub trait Element 32 | where 33 | Self: Sized + 'static, 34 | { 35 | type BlankProps: Default; 36 | 37 | /// Draw to the canvas 38 | 39 | fn render(&mut self, this: &ElModel!(), mut content: RenderElement) -> Result<()> { 40 | let _ = this; 41 | content.render_children() 42 | } 43 | 44 | fn set_children(&self, this: &ElModel!()) { 45 | this.set_children(()).layout(|()| unreachable!()).unwrap(); 46 | } 47 | 48 | fn draw_region_changed(&mut self, this: &ElModel!(), draw_region: Region) { 49 | if let Some(lc) = this.layout_children() { 50 | lc.layout_once(draw_region) 51 | .expect("child elements are more than 1") 52 | } 53 | } 54 | } 55 | 56 | pub trait ElementUpdate: Element + Sized { 57 | fn el_create(this: &ElModel!(), props: Pr) -> Self; 58 | fn el_update(&mut self, this: &ElModel!(), props: Pr, equality_matters: bool) -> bool; 59 | } 60 | 61 | pub trait AsChildren: RenderMultiple {} 62 | impl AsChildren for T {} 63 | -------------------------------------------------------------------------------- /irisia/src/element/props/defaulter.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | pub trait Defaulter { 4 | type Src; 5 | 6 | fn with_defaulter(self, defaulter: F) -> Self::Src 7 | where 8 | F: FnOnce() -> Self::Src; 9 | } 10 | 11 | #[derive(Clone, Copy)] 12 | pub struct PropNotInitialized(pub(super) PhantomData); 13 | 14 | impl Defaulter for PropNotInitialized { 15 | type Src = S; 16 | fn with_defaulter(self, defaulter: F) -> Self::Src 17 | where 18 | F: FnOnce() -> Self::Src, 19 | { 20 | defaulter() 21 | } 22 | } 23 | 24 | pub struct PropInitialized(pub S); 25 | 26 | impl Defaulter for PropInitialized { 27 | type Src = S; 28 | fn with_defaulter(self, _: F) -> Self::Src 29 | where 30 | F: FnOnce() -> Self::Src, 31 | { 32 | self.0 33 | } 34 | } 35 | 36 | impl PropInitialized { 37 | pub fn must_be_initialized(self) -> S { 38 | self.0 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /irisia/src/element/props/help_create.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{style::StyleContainer, StyleReader, UpdateWith}; 4 | 5 | use super::*; 6 | 7 | pub trait HelpCreate { 8 | type Def: Defaulter; 9 | fn create(&self, maybe_init: T) -> Self::Def; 10 | } 11 | 12 | impl HelpCreate for CallUpdater 13 | where 14 | S: UpdateWith, 15 | { 16 | type Def = PropInitialized; 17 | fn create(&self, maybe_init: (T,)) -> Self::Def { 18 | PropInitialized(S::create_with(maybe_init.0)) 19 | } 20 | } 21 | 22 | impl HelpCreate for MoveOwnership { 23 | type Def = PropInitialized; 24 | fn create(&self, maybe_init: (T,)) -> Self::Def { 25 | PropInitialized(maybe_init.0) 26 | } 27 | } 28 | 29 | impl HelpCreate for ReadStyle 30 | where 31 | S: StyleReader, 32 | T: StyleContainer, 33 | { 34 | type Def = PropInitialized; 35 | fn create(&self, maybe_init: (T,)) -> Self::Def { 36 | PropInitialized(S::read_style(&maybe_init.0)) 37 | } 38 | } 39 | 40 | impl HelpCreate for fn(T) -> S { 41 | type Def = PropInitialized; 42 | fn create(&self, maybe_init: (T,)) -> Self::Def { 43 | PropInitialized(self(maybe_init.0)) 44 | } 45 | } 46 | 47 | impl HelpCreate for M { 48 | type Def = PropNotInitialized; 49 | fn create(&self, _: ()) -> Self::Def { 50 | PropNotInitialized(PhantomData) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /irisia/src/element/props/help_update.rs: -------------------------------------------------------------------------------- 1 | use crate::{style::StyleContainer, StyleReader, UpdateWith}; 2 | 3 | use super::*; 4 | 5 | pub trait HelpUpdate: HelpCreate { 6 | fn update(&self, source: &mut S, maybe_init: T, equality_matters: bool) -> bool; 7 | } 8 | 9 | impl HelpUpdate for CallUpdater 10 | where 11 | S: UpdateWith, 12 | { 13 | fn update(&self, source: &mut S, maybe_init: (T,), equality_matters: bool) -> bool { 14 | source.update_with(maybe_init.0, equality_matters) 15 | } 16 | } 17 | 18 | impl HelpUpdate for MoveOwnership 19 | where 20 | T: PartialEq, 21 | { 22 | fn update(&self, source: &mut T, maybe_init: (T,), equality_matters: bool) -> bool { 23 | let eq = equality_matters && *source == maybe_init.0; 24 | *source = maybe_init.0; 25 | eq 26 | } 27 | } 28 | 29 | impl HelpUpdate for ReadStyle 30 | where 31 | S: StyleReader + PartialEq, 32 | T: StyleContainer, 33 | { 34 | fn update(&self, source: &mut S, maybe_init: (T,), equality_matters: bool) -> bool { 35 | let style = S::read_style(&maybe_init.0); 36 | let eq = equality_matters && style == *source; 37 | *source = style; 38 | eq 39 | } 40 | } 41 | 42 | impl HelpUpdate for fn(T) -> S 43 | where 44 | S: PartialEq, 45 | { 46 | fn update(&self, source: &mut S, maybe_init: (T,), equality_matters: bool) -> bool { 47 | let value = self(maybe_init.0); 48 | let eq = equality_matters && *source == value; 49 | *source = value; 50 | eq 51 | } 52 | } 53 | 54 | impl HelpUpdate for M { 55 | fn update(&self, _: &mut S, _: (), equality_matters: bool) -> bool { 56 | equality_matters 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /irisia/src/element/props/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::{ 2 | defaulter::{Defaulter, PropInitialized, PropNotInitialized}, 3 | help_create::HelpCreate, 4 | help_update::HelpUpdate, 5 | set_std_styles::SetStdStyles, 6 | }; 7 | 8 | pub struct CallUpdater; 9 | pub struct MoveOwnership; 10 | pub struct ReadStyle; 11 | 12 | mod defaulter; 13 | mod help_create; 14 | mod help_update; 15 | mod set_std_styles; 16 | 17 | pub trait PropsUpdateResult { 18 | type UpdateResult; 19 | } 20 | 21 | pub trait PropsUpdateWith: PropsUpdateResult { 22 | fn props_update_with(&mut self, updater: T) -> Self::UpdateResult; 23 | fn props_create_with(updater: T) -> Self; 24 | } 25 | -------------------------------------------------------------------------------- /irisia/src/element/props/set_std_styles.rs: -------------------------------------------------------------------------------- 1 | use crate::style::StyleContainer; 2 | 3 | pub trait SetStdStyles<'a, T> 4 | where 5 | T: StyleContainer, 6 | { 7 | type Output; 8 | fn set_std_styles(self, styles: &'a T) -> Self::Output; 9 | } 10 | 11 | impl<'a, T> SetStdStyles<'a, T> for () 12 | where 13 | T: StyleContainer, 14 | { 15 | type Output = (); 16 | fn set_std_styles(self, _: &T) -> Self::Output {} 17 | } 18 | -------------------------------------------------------------------------------- /irisia/src/element/render_element.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use anyhow::anyhow; 4 | use irisia_backend::skia_safe::Canvas; 5 | 6 | use crate::{ 7 | dom::{children::RenderMultiple, layer::LayerRebuilder}, 8 | Result, 9 | }; 10 | 11 | pub struct RenderElement<'a, 'lr> { 12 | lr: &'a mut LayerRebuilder<'lr>, 13 | children: Option>, 14 | interval: Duration, 15 | } 16 | 17 | impl<'a, 'lr> RenderElement<'a, 'lr> { 18 | pub(crate) fn new( 19 | lr: &'a mut LayerRebuilder<'lr>, 20 | children: Option<&'a mut dyn RenderMultiple>, 21 | interval: Duration, 22 | ) -> Self { 23 | RenderElement { 24 | lr, 25 | children: Some(children), 26 | interval, 27 | } 28 | } 29 | 30 | pub fn interval(&self) -> Duration { 31 | self.interval 32 | } 33 | 34 | pub fn render_children(&mut self) -> Result<()> { 35 | match self.children.take() { 36 | Some(children) => { 37 | if let Some(c) = children { 38 | c.render(self.lr, self.interval) 39 | } else { 40 | Ok(()) 41 | } 42 | } 43 | None => Err(anyhow!("children cannot be rendered more than once")), 44 | } 45 | } 46 | 47 | pub fn canvas(&mut self) -> &mut Canvas { 48 | self.lr.draw_in_place() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /irisia/src/event/event_dispatcher/extension.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use crate::event::standard::{ 4 | Click, PointerDown, PointerEntered, PointerMove, PointerOut, PointerUp, 5 | }; 6 | 7 | use super::EventDispatcher; 8 | 9 | impl EventDispatcher { 10 | pub async fn hover(&self) { 11 | tokio::select! { 12 | _ = self.recv_trusted::() => {}, 13 | _ = self.recv_trusted::() => {} 14 | } 15 | 16 | loop { 17 | tokio::select! { 18 | _ = self.recv_trusted::() => { 19 | self.recv_trusted::().await; 20 | } 21 | _ = self.recv_trusted::() => { 22 | self.recv_trusted::().await; 23 | } 24 | _ = tokio::time::sleep(Duration::from_secs(1)) => { 25 | break; 26 | } 27 | } 28 | } 29 | } 30 | 31 | pub async fn hover_canceled(&self) { 32 | tokio::select! { 33 | _ = self.recv_trusted::() => {}, 34 | _ = self.recv_trusted::() => {} 35 | } 36 | } 37 | 38 | pub async fn double_click(&self) { 39 | loop { 40 | self.recv_trusted::().await; 41 | if tokio::time::timeout(Duration::from_millis(400), self.recv_trusted::()) 42 | .await 43 | .is_ok() 44 | { 45 | break; 46 | } 47 | } 48 | } 49 | 50 | pub async fn hold(&self) { 51 | loop { 52 | self.recv_trusted::().await; 53 | if tokio::time::timeout(Duration::from_secs(1), self.recv_trusted::()) 54 | .await 55 | .is_err() 56 | { 57 | break; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /irisia/src/event/event_dispatcher/lock.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::{event::EventReceive, Event}; 4 | 5 | use super::{maybe_confirmed::MaybeConfirmed, EventDispatcher}; 6 | 7 | pub struct EventDispatcherLock<'a> { 8 | ed: &'a EventDispatcher, 9 | wait_lock: Arc, 10 | } 11 | 12 | impl<'a> EventDispatcherLock<'a> { 13 | pub fn from_event_dispatcher(ed: &'a EventDispatcher) -> Self { 14 | let wait_lock = ed.0.lock().unwrap().wait_lock().clone(); 15 | wait_lock.cancel_one(); 16 | EventDispatcherLock { ed, wait_lock } 17 | } 18 | 19 | pub fn recv(&mut self) -> EventReceive { 20 | let id = self 21 | .ed 22 | .0 23 | .lock() 24 | .unwrap() 25 | .stock() 26 | .get_or_insert::() 27 | .register(true); 28 | self.wait_lock.confirm_one(); 29 | EventReceive::new(self.ed, id) 30 | } 31 | 32 | pub async fn recv_trusted(&mut self) -> E { 33 | loop { 34 | let (ev, metadata) = self.recv::().await; 35 | if metadata.is_trusted_event { 36 | return ev; 37 | } 38 | } 39 | } 40 | } 41 | 42 | impl Drop for EventDispatcherLock<'_> { 43 | fn drop(&mut self) { 44 | self.wait_lock.confirm_one(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /irisia/src/event/event_dispatcher/maybe_confirmed.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::AtomicUsize; 2 | 3 | use tokio::sync::{Semaphore, TryAcquireError}; 4 | 5 | // only when all permits taken back can emit events 6 | pub(crate) struct MaybeConfirmed { 7 | confirmed: Semaphore, 8 | defer_cancel: AtomicUsize, 9 | } 10 | 11 | const MAX_LOCKS: usize = 1000000; 12 | 13 | impl MaybeConfirmed { 14 | pub fn new() -> Self { 15 | MaybeConfirmed { 16 | confirmed: Semaphore::new(MAX_LOCKS), 17 | defer_cancel: AtomicUsize::new(0), 18 | } 19 | } 20 | 21 | pub fn confirm_one(&self) { 22 | self.confirmed.add_permits(1); 23 | } 24 | 25 | pub fn cancel_one(&self) { 26 | self.cancel_many(1); 27 | } 28 | 29 | pub fn cancel_many(&self, count: usize) { 30 | match self.confirmed.try_acquire_many(count as _) { 31 | Ok(permit) => permit.forget(), 32 | Err(_) => { 33 | self.defer_cancel 34 | .fetch_add(count, std::sync::atomic::Ordering::Relaxed); 35 | } 36 | } 37 | } 38 | 39 | pub async fn all_confirmed(&self) -> AllConfirmedPermits { 40 | loop { 41 | self.confirmed 42 | .acquire_many(MAX_LOCKS as _) 43 | .await 44 | .unwrap() 45 | .forget(); 46 | 47 | let defer_cancel_count = self 48 | .defer_cancel 49 | .swap(0, std::sync::atomic::Ordering::Relaxed); 50 | 51 | if defer_cancel_count == 0 { 52 | break; 53 | } 54 | 55 | self.confirmed 56 | .add_permits((MAX_LOCKS - defer_cancel_count) as _); 57 | } 58 | 59 | AllConfirmedPermits { 60 | maybe_confirmed: self, 61 | semaphore_permits: MAX_LOCKS, 62 | } 63 | } 64 | 65 | pub fn try_all_confirmed(&self) -> Result { 66 | self.confirmed.try_acquire_many(MAX_LOCKS as _)?.forget(); 67 | 68 | let defer_cancel_count = self 69 | .defer_cancel 70 | .swap(0, std::sync::atomic::Ordering::Relaxed); 71 | 72 | if defer_cancel_count != 0 { 73 | self.confirmed 74 | .add_permits((MAX_LOCKS - defer_cancel_count) as _); 75 | return Err(TryAcquireError::NoPermits); 76 | } 77 | 78 | Ok(AllConfirmedPermits { 79 | maybe_confirmed: self, 80 | semaphore_permits: MAX_LOCKS, 81 | }) 82 | } 83 | } 84 | 85 | pub struct AllConfirmedPermits<'a> { 86 | maybe_confirmed: &'a MaybeConfirmed, 87 | semaphore_permits: usize, 88 | } 89 | 90 | impl AllConfirmedPermits<'_> { 91 | pub fn cancel_many(&mut self, count: usize) { 92 | assert_ne!(count, MAX_LOCKS); 93 | self.semaphore_permits -= count; 94 | } 95 | } 96 | 97 | impl Drop for AllConfirmedPermits<'_> { 98 | fn drop(&mut self) { 99 | self.maybe_confirmed 100 | .confirmed 101 | .add_permits(self.semaphore_permits as _); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /irisia/src/event/event_dispatcher/mod.rs: -------------------------------------------------------------------------------- 1 | use self::{lock::EventDispatcherLock, scheduler::EmitScheduler}; 2 | use crate::Event; 3 | use std::sync::{Arc, Mutex as StdMutex}; 4 | 5 | use super::{listen::Listen, EventMetadata, EventReceive}; 6 | 7 | mod extension; 8 | pub mod lock; 9 | mod maybe_confirmed; 10 | pub mod receive; 11 | mod scheduler; 12 | 13 | #[derive(Clone)] 14 | pub struct EventDispatcher(Arc>); 15 | 16 | impl EventDispatcher { 17 | pub fn new() -> Self { 18 | EventDispatcher(Arc::new(StdMutex::new(EmitScheduler::new()))) 19 | } 20 | 21 | pub fn emit(&self, event: impl Event) { 22 | EmitScheduler::emit_raw(&self.0, event, EventMetadata::new()); 23 | } 24 | 25 | pub(crate) fn emit_trusted(&self, event: E) { 26 | EmitScheduler::emit_raw(&self.0, event, EventMetadata::new_trusted()); 27 | } 28 | 29 | pub fn recv(&self) -> EventReceive { 30 | let id = self 31 | .0 32 | .lock() 33 | .unwrap() 34 | .stock() 35 | .get_or_insert::() 36 | .register(false); 37 | EventReceive::new(self, id) 38 | } 39 | 40 | pub async fn recv_trusted(&self) -> E { 41 | loop { 42 | let (ev, metadata) = self.recv::().await; 43 | if metadata.is_trusted_event { 44 | return ev; 45 | } 46 | } 47 | } 48 | 49 | pub fn listen(&self) -> Listen { 50 | Listen::new(self) 51 | } 52 | 53 | pub(crate) fn ptr_eq(&self, other: &EventDispatcher) -> bool { 54 | Arc::ptr_eq(&self.0, &other.0) 55 | } 56 | 57 | pub fn is_same(&self, other: &EventDispatcher) -> bool { 58 | Arc::ptr_eq(&self.0, &other.0) 59 | } 60 | 61 | pub fn lock(&self) -> EventDispatcherLock { 62 | EventDispatcherLock::from_event_dispatcher(self) 63 | } 64 | } 65 | 66 | impl Default for EventDispatcher { 67 | fn default() -> Self { 68 | Self::new() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /irisia/src/event/event_dispatcher/receive.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, marker::PhantomData, task::Poll}; 2 | 3 | use crate::{event::EventMetadata, Event}; 4 | 5 | use super::EventDispatcher; 6 | 7 | pub struct EventReceive<'ed, E: Event> { 8 | _phantom: PhantomData, 9 | dispatcher: &'ed EventDispatcher, 10 | id: u32, 11 | taken: bool, 12 | } 13 | 14 | impl<'ed, E: Event> EventReceive<'ed, E> { 15 | pub(super) fn new(dispatcher: &'ed EventDispatcher, id: u32) -> Self { 16 | EventReceive { 17 | _phantom: PhantomData, 18 | dispatcher, 19 | id, 20 | taken: false, 21 | } 22 | } 23 | } 24 | 25 | impl Future for EventReceive<'_, E> { 26 | type Output = (E, EventMetadata); 27 | fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { 28 | assert!(!self.taken); 29 | 30 | match self 31 | .dispatcher 32 | .0 33 | .lock() 34 | .unwrap() 35 | .stock() 36 | .get_exist::() 37 | .poll(self.id, cx.waker().clone()) 38 | { 39 | Some(pair) => { 40 | self.get_mut().taken = true; 41 | Poll::Ready(pair) 42 | } 43 | 44 | None => Poll::Pending, 45 | } 46 | } 47 | } 48 | 49 | impl Drop for EventReceive<'_, E> { 50 | fn drop(&mut self) { 51 | if !self.taken { 52 | self.dispatcher 53 | .0 54 | .lock() 55 | .unwrap() 56 | .stock() 57 | .get_exist::() 58 | .clear_by_id(self.id); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /irisia/src/event/event_dispatcher/scheduler/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::VecDeque, 3 | sync::{Arc, Mutex}, 4 | }; 5 | 6 | use tokio::task::JoinHandle; 7 | 8 | use crate::{event::EventMetadata, Event}; 9 | 10 | use super::maybe_confirmed::{AllConfirmedPermits, MaybeConfirmed}; 11 | use stock::EventListenerStock; 12 | 13 | pub(super) mod stock; 14 | 15 | pub(super) struct EmitScheduler { 16 | stock: EventListenerStock, 17 | executor: Option>, 18 | event_queue: VecDeque, 19 | wait_lock: Arc, 20 | } 21 | 22 | struct QueuedEvent { 23 | #[allow(clippy::type_complexity)] 24 | event: Box, 25 | } 26 | 27 | impl EmitScheduler { 28 | pub fn new() -> Self { 29 | let arc = Arc::new(MaybeConfirmed::new()); 30 | EmitScheduler { 31 | stock: EventListenerStock::new(arc.clone()), 32 | executor: None, 33 | event_queue: Default::default(), 34 | wait_lock: arc, 35 | } 36 | } 37 | 38 | pub(super) fn emit_raw(this: &Arc>, event: E, metadata: EventMetadata) { 39 | let mut guard = this.lock().unwrap(); 40 | 41 | // there is existing executor 42 | if guard.executor.is_some() { 43 | guard.event_queue.push_back(QueuedEvent { 44 | event: Box::new(move |scheduler, permits| { 45 | if let Some(item) = scheduler.stock.get() { 46 | item.finish(event, metadata, permits); 47 | } 48 | }), 49 | }); 50 | return; 51 | } 52 | 53 | // there is no existing executor, but can execute immediately 54 | let guard_ref = &mut *guard; 55 | if let Ok(permits) = guard_ref.wait_lock.try_all_confirmed() { 56 | if let Some(item) = guard_ref.stock.get() { 57 | item.finish(event, metadata, permits); 58 | } 59 | return; 60 | } 61 | 62 | // there is no existing executor, also has lock held 63 | guard_ref.spawn_executor(this, event, metadata); 64 | } 65 | 66 | fn spawn_executor( 67 | &mut self, 68 | this: &Arc>, 69 | event: E, 70 | metadata: EventMetadata, 71 | ) { 72 | let this = this.clone(); 73 | let wait_lock = self.wait_lock.clone(); 74 | let handle = tokio::task::spawn_local(async move { 75 | let mut next_event = { 76 | let permits = wait_lock.all_confirmed().await; 77 | let mut guard = this.lock().unwrap(); 78 | if let Some(item) = guard.stock.get() { 79 | item.finish(event, metadata, permits); 80 | } 81 | 82 | match guard.event_queue.pop_front() { 83 | Some(ev) => ev, 84 | None => { 85 | guard.executor = None; 86 | return; 87 | } 88 | } 89 | }; 90 | 91 | loop { 92 | let permits = wait_lock.all_confirmed().await; 93 | let mut guard = this.lock().unwrap(); 94 | match guard.event_queue.pop_front() { 95 | Some(next) => { 96 | let current_event = std::mem::replace(&mut next_event, next); 97 | (current_event.event)(&mut guard, permits); 98 | } 99 | None => { 100 | (next_event.event)(&mut guard, permits); 101 | guard.executor = None; 102 | return; 103 | } 104 | } 105 | } 106 | }); 107 | 108 | if !handle.is_finished() { 109 | self.executor = Some(handle); 110 | } 111 | } 112 | 113 | pub(super) fn stock(&mut self) -> &mut EventListenerStock { 114 | &mut self.stock 115 | } 116 | 117 | pub(super) fn wait_lock(&self) -> &Arc { 118 | &self.wait_lock 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /irisia/src/event/listen.rs: -------------------------------------------------------------------------------- 1 | use irisia_macros::__inner_impl_listen; 2 | use std::future::Future; 3 | use tokio::task::JoinHandle; 4 | 5 | use crate::{ 6 | event::{EventDispatcher, EventReceiver, SubEvent}, 7 | Event, 8 | }; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct Listen<'a, Ep, T0 = (), T1 = (), T2 = (), T3 = (), T4 = ()> { 12 | ep: &'a Ep, 13 | once: T0, 14 | trusted: T1, 15 | asyn: T2, 16 | sub_event: T3, 17 | no_handle: T4, 18 | } 19 | 20 | #[derive(Default, Clone, Copy)] 21 | pub struct FlagSet; 22 | 23 | impl<'a, Ep> Listen<'a, Ep> { 24 | pub(crate) fn new(ep: &'a Ep) -> Self { 25 | Listen { 26 | ep, 27 | once: (), 28 | trusted: (), 29 | asyn: (), 30 | sub_event: (), 31 | no_handle: (), 32 | } 33 | } 34 | } 35 | 36 | macro_rules! auto_fn { 37 | ($($name:ident: $t0:ident $t1:ident $t2:ident $t3:ident $t4:ident,)*) => { 38 | $( 39 | pub fn $name(self) -> Listen<'a, Ep, $t0, $t1, $t2, $t3, $t4> { 40 | Listen { 41 | ep: self.ep, 42 | once: choose_value!($t0 self.once), 43 | trusted: choose_value!($t1 self.trusted), 44 | asyn: choose_value!($t2 self.asyn), 45 | sub_event: choose_value!($t3 self.sub_event), 46 | no_handle: choose_value!($t4 self.no_handle), 47 | } 48 | } 49 | )* 50 | }; 51 | } 52 | 53 | macro_rules! choose_value { 54 | (FlagSet $expr:expr) => { 55 | FlagSet 56 | }; 57 | ($_:ident $expr:expr) => { 58 | $expr 59 | }; 60 | } 61 | 62 | impl<'a, Ep, T0, T1, T2, T3, T4> Listen<'a, Ep, T0, T1, T2, T3, T4> { 63 | auto_fn! { 64 | once: FlagSet T1 T2 T3 T4, 65 | trusted: T0 FlagSet T2 T3 T4, 66 | asyn: T0 T1 FlagSet T3 T4, 67 | sub_event: T0 T1 T2 FlagSet T4, 68 | no_handle: T0 T1 T2 T3 FlagSet, 69 | } 70 | } 71 | 72 | pub trait EdProvider: Clone + 'static { 73 | fn event_dispatcher(&self) -> &EventDispatcher; 74 | fn daemon(&self, f: F) -> JoinHandle<()> 75 | where 76 | F: Future + 'static; 77 | fn handle_available(&self) -> bool; 78 | } 79 | 80 | impl EdProvider for EventDispatcher { 81 | fn event_dispatcher(&self) -> &EventDispatcher { 82 | self 83 | } 84 | 85 | fn daemon(&self, f: F) -> JoinHandle<()> 86 | where 87 | F: Future + 'static, 88 | { 89 | tokio::task::spawn_local(async move { 90 | f.await; 91 | }) 92 | } 93 | 94 | fn handle_available(&self) -> bool { 95 | true 96 | } 97 | } 98 | 99 | __inner_impl_listen!(); 100 | -------------------------------------------------------------------------------- /irisia/src/event/metadata.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy)] 2 | pub struct EventMetadata { 3 | pub(crate) is_trusted_event: bool, 4 | } 5 | 6 | impl EventMetadata { 7 | pub fn new() -> Self { 8 | EventMetadata { 9 | is_trusted_event: false, 10 | } 11 | } 12 | 13 | pub(crate) fn new_trusted() -> Self { 14 | EventMetadata { 15 | is_trusted_event: true, 16 | } 17 | } 18 | 19 | pub fn is_trusted_event(&self) -> bool { 20 | self.is_trusted_event 21 | } 22 | } 23 | 24 | impl Default for EventMetadata { 25 | fn default() -> Self { 26 | Self::new() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /irisia/src/event/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::{ 2 | event_dispatcher::{receive::EventReceive, EventDispatcher}, 3 | listen::Listen, 4 | metadata::EventMetadata, 5 | }; 6 | use async_trait::async_trait; 7 | 8 | use self::event_dispatcher::lock::EventDispatcherLock; 9 | pub(crate) use listen::EdProvider; 10 | 11 | pub mod event_dispatcher; 12 | mod listen; 13 | pub mod metadata; 14 | pub mod standard; 15 | 16 | pub trait Event: Sized + Send + Unpin + Clone + 'static {} 17 | 18 | #[async_trait] 19 | pub trait SubEvent { 20 | async fn handle(ed: &mut EventReceiver) -> Self; 21 | } 22 | 23 | pub enum EventReceiver<'a> { 24 | EventDispatcher(&'a EventDispatcher), 25 | Lock(EventDispatcherLock<'a>), 26 | } 27 | 28 | impl EventReceiver<'_> { 29 | pub fn recv(&mut self) -> EventReceive { 30 | match self { 31 | Self::EventDispatcher(ed) => ed.recv(), 32 | Self::Lock(lock) => lock.recv(), 33 | } 34 | } 35 | 36 | pub async fn recv_trusted(&mut self) -> E { 37 | match self { 38 | Self::EventDispatcher(ed) => ed.recv_trusted().await, 39 | Self::Lock(lock) => lock.recv_trusted().await, 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /irisia/src/event/standard/mod.rs: -------------------------------------------------------------------------------- 1 | use irisia_backend::window_handle::CloseHandle; 2 | use irisia_backend::StaticWindowEvent; 3 | 4 | use crate as irisia; 5 | use crate::primitive::{Pixel, Point}; 6 | use crate::Event; 7 | 8 | //pub mod window_event; 9 | 10 | impl Event for StaticWindowEvent {} 11 | 12 | /// Declares the element won't be used by the origin structure anymore, 13 | /// but may not dropped immediately due to strong references of `Arc`. 14 | /// This event will be emitted only 15 | /// once, when received, runtimes of this element should handle quiting. 16 | #[derive(Event, Clone, Copy)] 17 | pub struct ElementAbandoned; 18 | 19 | #[derive(Event, Clone, Copy)] 20 | pub struct Blured; 21 | 22 | #[derive(Event, Clone, Copy)] 23 | pub struct Focused; 24 | 25 | #[derive(Event, Clone)] 26 | pub struct PointerDown { 27 | pub is_current: bool, 28 | pub position: Point, 29 | } 30 | 31 | #[derive(Event, Clone, Copy)] 32 | pub struct PointerUp { 33 | pub is_current: bool, 34 | pub position: Point, 35 | } 36 | 37 | #[derive(Event, Clone, Copy)] 38 | pub struct PointerMove { 39 | pub is_current: bool, 40 | pub delta: (Pixel, Pixel), 41 | pub position: Point, 42 | } 43 | 44 | #[derive(Event, Clone, Copy)] 45 | pub struct PointerEntered; 46 | 47 | #[derive(Event, Clone, Copy)] 48 | pub struct PointerOut; 49 | 50 | #[derive(Event, Clone, Copy)] 51 | pub struct PointerOver; 52 | 53 | #[derive(Event, Clone, Copy)] 54 | pub struct PointerLeft; 55 | 56 | #[derive(Event, Clone, Copy)] 57 | pub struct Click { 58 | pub is_current: bool, 59 | } 60 | 61 | #[derive(Event, Clone, Copy)] 62 | pub struct CloseRequested(pub CloseHandle); 63 | 64 | #[derive(Event, Clone, Copy)] 65 | pub struct WindowDestroyed; 66 | -------------------------------------------------------------------------------- /irisia/src/event/standard/window_event.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use crate as irisia; 4 | use crate::Event; 5 | use irisia_backend::{ 6 | winit::{ 7 | dpi::{PhysicalPosition, PhysicalSize}, 8 | event::{ 9 | DeviceId, ElementState, Ime, KeyboardInput, ModifiersState, MouseButton, 10 | MouseScrollDelta, Touch, TouchPhase, 11 | }, 12 | window::Theme, 13 | }, 14 | StaticWindowEvent, 15 | }; 16 | 17 | impl Event for StaticWindowEvent {} 18 | 19 | macro_rules! impl_window_event { 20 | ()=>{}; 21 | 22 | ($Struct:ident ($($tt:tt)*), $($rest:tt)*) => { 23 | #[derive(Clone, Event)] 24 | pub struct $Struct($($tt)*); 25 | impl_window_event!($($rest)*); 26 | }; 27 | 28 | ($Struct:ident {$($tt:tt)*}, $($rest:tt)*) => { 29 | #[derive(Clone, Event)] 30 | pub struct $Struct{$($tt)*} 31 | impl_window_event!($($rest)*); 32 | }; 33 | 34 | ($Struct:ident, $($rest:tt)*) => { 35 | #[derive(Clone, Event)] 36 | pub struct $Struct; 37 | impl_window_event!($($rest)*); 38 | }; 39 | } 40 | 41 | impl_window_event! { 42 | WindowResized(pub PhysicalSize), 43 | WindowMoved(pub PhysicalPosition), 44 | WindowCloseRequested, 45 | WindowDestroyed, 46 | WindowDroppedFile(pub PathBuf), 47 | WindowHoveredFile(pub PathBuf), 48 | WindowHoveredFileCancelled, 49 | WindowReceivedCharacter(pub char), 50 | WindowFocused(pub bool), 51 | WindowKeyboardInput { 52 | pub device_id: DeviceId, 53 | pub input: KeyboardInput, 54 | pub is_synthetic: bool, 55 | }, 56 | WindowModifiersChanged(ModifiersState), 57 | WindowIme(Ime), 58 | WindowCursorMoved { 59 | pub device_id: DeviceId, 60 | pub position: PhysicalPosition, 61 | pub modifiers: ModifiersState, 62 | }, 63 | WindowCursorEntered { 64 | pub device_id: DeviceId, 65 | }, 66 | WindowCursorLeft { 67 | pub device_id: DeviceId, 68 | }, 69 | WindowMouseWheel { 70 | pub device_id: DeviceId, 71 | pub delta: MouseScrollDelta, 72 | pub phase: TouchPhase, 73 | pub modifiers: ModifiersState, 74 | }, 75 | WindowMouseInput { 76 | pub device_id: DeviceId, 77 | pub state: ElementState, 78 | pub button: MouseButton, 79 | pub modifiers: ModifiersState, 80 | }, 81 | WindowTouch(Touch), 82 | WindowScaleFactorChanged { 83 | pub scale_factor: f64, 84 | pub new_inner_size: PhysicalSize, 85 | }, 86 | WindowThemeChanged(Theme), 87 | } 88 | -------------------------------------------------------------------------------- /irisia/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod anyhow { 2 | pub use anyhow::*; 3 | } 4 | 5 | pub type Result = anyhow::Result; 6 | 7 | #[doc(hidden)] 8 | #[path = "private/mod.rs"] 9 | pub mod __private; 10 | 11 | macro_rules! inner_error { 12 | ($($tt:tt)+) => { 13 | ::std::panic!("[IRISIA_INNER_ERROR {}: {}] {}", ::std::file!(), ::std::line!(), ::std::format!($($tt)+)) 14 | }; 15 | } 16 | 17 | pub mod application; 18 | pub(crate) mod dom; 19 | pub mod element; 20 | pub mod event; 21 | pub mod log; 22 | pub mod primitive; 23 | pub mod structure; 24 | pub mod style; 25 | pub mod update_with; 26 | 27 | pub use application::Window; 28 | pub use element::Element; 29 | pub use event::Event; 30 | pub use irisia_backend::{ 31 | runtime::exit_app, skia_safe, start_runtime, winit, StaticWindowEvent, WinitWindow, 32 | }; 33 | pub use irisia_macros::{build, main, props, style, Event, Style, StyleReader}; 34 | pub use style::{reader::StyleReader, Style}; 35 | pub use update_with::UpdateWith; 36 | -------------------------------------------------------------------------------- /irisia/src/log/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! info { 3 | ($($tt:tt)+) => { 4 | $crate::log::println!("INFO", $($tt)*); 5 | }; 6 | } 7 | 8 | #[macro_export] 9 | macro_rules! warning { 10 | ($($tt:tt)+) => { 11 | $crate::log::println!("WARNING", $($tt)*); 12 | }; 13 | } 14 | 15 | #[macro_export] 16 | macro_rules! error { 17 | ($($tt:tt)+) => { 18 | $crate::log::println!("ERROR", $($tt)*); 19 | }; 20 | } 21 | 22 | #[doc(hidden)] 23 | #[macro_export] 24 | macro_rules! println { 25 | ($prefix:literal, $fmt:literal $($tt:tt)*) => { 26 | println!(::std::concat!("[{} {}: {}] ", $fmt), $prefix, ::std::file!(), ::std::line!() $($tt)*) 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /irisia/src/primitive/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::{pixel::Pixel, point::Point}; 2 | 3 | pub mod pixel; 4 | pub mod point; 5 | 6 | pub type Result = anyhow::Result; 7 | pub type Region = (Point, Point); 8 | -------------------------------------------------------------------------------- /irisia/src/primitive/pixel.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Debug, 3 | ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}, 4 | }; 5 | 6 | /// `Pixel` is a logical length, difference from physical length. 7 | /// 8 | /// Except drawing something on canvas, at everywhere should use 9 | /// `Pixel` instead of primitive number to express a logical length. 10 | #[derive(Default, Clone, Copy, PartialEq, PartialOrd)] 11 | pub struct Pixel(pub f32); 12 | 13 | const DPI: f32 = 1.0; 14 | 15 | impl Pixel { 16 | pub fn to_physical(self) -> f32 { 17 | self.0 * DPI 18 | } 19 | 20 | pub fn from_physical(p: f32) -> Self { 21 | Pixel(p) / DPI 22 | } 23 | 24 | pub fn min(self, other: Self) -> Self { 25 | Pixel(self.0.min(other.0)) 26 | } 27 | 28 | pub fn max(self, other: Self) -> Self { 29 | Pixel(self.0.max(other.0)) 30 | } 31 | } 32 | 33 | impl Debug for Pixel { 34 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 35 | write!(f, "{}px", self.0) 36 | } 37 | } 38 | 39 | macro_rules! impl_opr { 40 | ($var:ident, 41 | $($Trait:ident $TraitAssign:ident $Rhs:ident $fnname:ident $fnassign:ident $opr:tt $vexpr:expr,)*) => { 42 | $( 43 | impl $Trait<$Rhs> for Pixel { 44 | type Output = Self; 45 | fn $fnname(self, $var: $Rhs) -> Self { 46 | Self(self.0 $opr $vexpr) 47 | } 48 | } 49 | 50 | impl $TraitAssign<$Rhs> for Pixel { 51 | fn $fnassign(&mut self, $var: $Rhs) { 52 | self.0 = self.0 $opr $vexpr; 53 | } 54 | } 55 | )* 56 | }; 57 | } 58 | 59 | impl_opr! { 60 | rhs, 61 | Add AddAssign Self add add_assign + rhs.0, 62 | Sub SubAssign Self sub sub_assign - rhs.0, 63 | Mul MulAssign f32 mul mul_assign * rhs, 64 | Div DivAssign f32 div div_assign / rhs, 65 | } 66 | 67 | impl Neg for Pixel { 68 | type Output = Self; 69 | fn neg(self) -> Self { 70 | Pixel(-self.0) 71 | } 72 | } 73 | 74 | impl From for Pixel { 75 | fn from(value: f32) -> Self { 76 | Pixel(value) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /irisia/src/primitive/point.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, AddAssign, Sub, SubAssign}; 2 | 3 | use irisia_backend::{skia_safe::Point as SkiaPoint, winit::dpi::PhysicalPosition}; 4 | 5 | use super::Pixel; 6 | 7 | #[derive(Debug, Default, Clone, Copy, PartialEq)] 8 | pub struct Point(pub Pixel, pub Pixel); 9 | 10 | impl Add for Point { 11 | type Output = Self; 12 | #[inline] 13 | fn add(self, rhs: Self) -> Self::Output { 14 | Point(self.0 + rhs.0, self.1 + rhs.1) 15 | } 16 | } 17 | 18 | impl AddAssign for Point { 19 | fn add_assign(&mut self, rhs: Self) { 20 | *self = *self + rhs; 21 | } 22 | } 23 | 24 | impl Sub for Point { 25 | type Output = Self; 26 | #[inline] 27 | fn sub(self, rhs: Self) -> Self::Output { 28 | Point(self.0 - rhs.0, self.1 - rhs.1) 29 | } 30 | } 31 | 32 | impl SubAssign for Point { 33 | fn sub_assign(&mut self, rhs: Self) { 34 | *self = *self - rhs; 35 | } 36 | } 37 | 38 | impl Point { 39 | pub fn abs_diff(self, other: Self) -> Pixel { 40 | Pixel(((self.0 - other.0).0.powi(2) + (self.1 - other.1).0.powi(2)).sqrt()) 41 | } 42 | 43 | /// Absolutely greater than or equals 44 | pub fn abs_ge(self, other: Self) -> bool { 45 | self.0 >= other.0 && self.1 >= other.1 46 | } 47 | 48 | /// Absolutely less than or equals 49 | pub fn abs_le(self, other: Self) -> bool { 50 | self.0 <= other.0 && self.1 <= other.1 51 | } 52 | } 53 | 54 | impl From<(Pixel, Pixel)> for Point { 55 | #[inline] 56 | fn from(f: (Pixel, Pixel)) -> Self { 57 | Point(f.0, f.1) 58 | } 59 | } 60 | 61 | impl From for (Pixel, Pixel) { 62 | fn from(v: Point) -> Self { 63 | (v.0, v.1) 64 | } 65 | } 66 | 67 | impl From> for Point { 68 | fn from(value: PhysicalPosition) -> Self { 69 | Point( 70 | Pixel::from_physical(value.x as _), 71 | Pixel::from_physical(value.y as _), 72 | ) 73 | } 74 | } 75 | 76 | impl From for SkiaPoint { 77 | fn from(value: Point) -> Self { 78 | SkiaPoint { 79 | x: value.0.to_physical(), 80 | y: value.1.to_physical(), 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /irisia/src/private/chain_caller.rs: -------------------------------------------------------------------------------- 1 | pub struct ChainCallHelper(T); 2 | 3 | impl ChainCallHelper { 4 | pub fn call_func(mut self, func: fn(&mut T, A), arg: A) -> Self { 5 | func(&mut self.0, arg); 6 | self 7 | } 8 | 9 | pub fn call_func_no_arg(mut self, func: fn(&mut T)) -> Self { 10 | func(&mut self.0); 11 | self 12 | } 13 | 14 | pub fn finish(self) -> T { 15 | self.0 16 | } 17 | } 18 | 19 | pub fn new_chain_caller(value: T) -> ChainCallHelper { 20 | ChainCallHelper(value) 21 | } 22 | -------------------------------------------------------------------------------- /irisia/src/private/for_loop.rs: -------------------------------------------------------------------------------- 1 | pub struct ForLoopIterItemAsKey { 2 | iter: I, 3 | map: F, 4 | } 5 | 6 | impl Iterator for ForLoopIterItemAsKey 7 | where 8 | I: Iterator, 9 | K: Clone, 10 | F: FnMut(K) -> T, 11 | { 12 | type Item = (K, T); 13 | fn next(&mut self) -> Option { 14 | self.iter 15 | .next() 16 | .map(|item| (item.clone(), (self.map)(item))) 17 | } 18 | } 19 | 20 | pub fn for_loop_iter_item_as_key(iter: I, map: F) -> ForLoopIterItemAsKey { 21 | ForLoopIterItemAsKey { iter, map } 22 | } 23 | -------------------------------------------------------------------------------- /irisia/src/private/mod.rs: -------------------------------------------------------------------------------- 1 | mod chain_caller; 2 | mod for_loop; 3 | 4 | pub use chain_caller::new_chain_caller; 5 | pub use for_loop::for_loop_iter_item_as_key; 6 | -------------------------------------------------------------------------------- /irisia/src/structure/chain.rs: -------------------------------------------------------------------------------- 1 | use super::{MapVisit, UpdateWith, Visit, VisitLen, VisitMut}; 2 | use crate::{update_with::SpecificUpdate, Result}; 3 | 4 | pub struct Chain { 5 | pub former: A, 6 | pub latter: B, 7 | } 8 | 9 | impl Chain { 10 | pub fn new(former: A, latter: B) -> Self { 11 | Chain { former, latter } 12 | } 13 | } 14 | 15 | impl MapVisit for Chain 16 | where 17 | A: MapVisit, 18 | B: MapVisit, 19 | { 20 | type Output = Chain; 21 | fn map(self, visitor: &V) -> Self::Output { 22 | Chain { 23 | former: self.former.map(visitor), 24 | latter: self.latter.map(visitor), 25 | } 26 | } 27 | } 28 | 29 | impl VisitLen for Chain 30 | where 31 | A: VisitLen, 32 | B: VisitLen, 33 | { 34 | fn len(&self) -> usize { 35 | self.former.len() + self.latter.len() 36 | } 37 | } 38 | 39 | impl Visit for Chain 40 | where 41 | A: Visit, 42 | B: Visit, 43 | { 44 | fn visit(&self, visitor: &mut V) -> Result<()> { 45 | self.former.visit(visitor)?; 46 | self.latter.visit(visitor) 47 | } 48 | } 49 | 50 | impl VisitMut for Chain 51 | where 52 | A: VisitMut, 53 | B: VisitMut, 54 | { 55 | fn visit_mut(&mut self, visitor: &mut V) -> Result<()> { 56 | self.former.visit_mut(visitor)?; 57 | self.latter.visit_mut(visitor) 58 | } 59 | } 60 | 61 | impl UpdateWith> for Chain 62 | where 63 | A: UpdateWith, 64 | B: UpdateWith, 65 | { 66 | fn create_with(updater: Chain) -> Self { 67 | Self { 68 | former: A::create_with(updater.former), 69 | latter: B::create_with(updater.latter), 70 | } 71 | } 72 | 73 | fn update_with(&mut self, updater: Chain, mut equality_matters: bool) -> bool { 74 | equality_matters &= self.former.update_with(updater.former, equality_matters); 75 | equality_matters &= self.latter.update_with(updater.latter, equality_matters); 76 | equality_matters 77 | } 78 | } 79 | 80 | impl SpecificUpdate for Chain 81 | where 82 | A: SpecificUpdate, 83 | B: SpecificUpdate, 84 | A::UpdateTo: UpdateWith, 85 | B::UpdateTo: UpdateWith, 86 | { 87 | type UpdateTo = Chain; 88 | } 89 | -------------------------------------------------------------------------------- /irisia/src/structure/empty.rs: -------------------------------------------------------------------------------- 1 | use super::{MapVisit, UpdateWith, Visit, VisitLen, VisitMut}; 2 | use crate::{update_with::SpecificUpdate, Result}; 3 | 4 | impl MapVisit for () { 5 | type Output = (); 6 | fn map(self, _: &V) {} 7 | } 8 | 9 | impl VisitLen for () { 10 | fn len(&self) -> usize { 11 | 0 12 | } 13 | } 14 | 15 | impl Visit for () { 16 | fn visit(&self, _: &mut V) -> Result<()> { 17 | Ok(()) 18 | } 19 | } 20 | 21 | impl VisitMut for () { 22 | fn visit_mut(&mut self, _: &mut V) -> Result<()> { 23 | Ok(()) 24 | } 25 | } 26 | 27 | impl UpdateWith<()> for () { 28 | fn create_with(_: ()) {} 29 | 30 | fn update_with(&mut self, _: (), equality_matters: bool) -> bool { 31 | equality_matters 32 | } 33 | } 34 | 35 | impl SpecificUpdate for () { 36 | type UpdateTo = (); 37 | } 38 | -------------------------------------------------------------------------------- /irisia/src/structure/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{update_with::UpdateWith, Result}; 2 | 3 | pub use self::{branch::Branch, chain::Chain, once::Once, repeat::Repeat}; 4 | 5 | pub mod branch; 6 | pub mod chain; 7 | pub mod empty; 8 | pub mod once; 9 | pub mod repeat; 10 | pub(crate) mod slot; 11 | 12 | pub trait VisitLen: Sized { 13 | fn len(&self) -> usize; 14 | fn is_empty(&self) -> bool { 15 | self.len() == 0 16 | } 17 | } 18 | 19 | pub trait Visit: VisitLen { 20 | fn visit(&self, visitor: &mut V) -> Result<()>; 21 | } 22 | 23 | pub trait Visitor: Sized { 24 | fn visit(&mut self, data: &T) -> Result<()>; 25 | } 26 | 27 | pub trait VisitMut: VisitLen { 28 | fn visit_mut(&mut self, visitor: &mut V) -> Result<()>; 29 | } 30 | 31 | pub trait VisitorMut: Sized { 32 | fn visit_mut(&mut self, data: &mut T) -> Result<()>; 33 | } 34 | 35 | pub trait MapVisit { 36 | type Output; 37 | fn map(self, visitor: &V) -> Self::Output; 38 | } 39 | 40 | pub trait MapVisitor { 41 | type Output; 42 | fn map_visit(&self, data: T) -> Self::Output; 43 | } 44 | -------------------------------------------------------------------------------- /irisia/src/structure/once.rs: -------------------------------------------------------------------------------- 1 | use super::{MapVisit, MapVisitor, UpdateWith, Visit, VisitLen, VisitMut, Visitor, VisitorMut}; 2 | use crate::{update_with::SpecificUpdate, Result}; 3 | 4 | pub struct Once(pub T); 5 | 6 | impl MapVisit for Once 7 | where 8 | V: MapVisitor, 9 | { 10 | type Output = Once; 11 | fn map(self, visitor: &V) -> Self::Output { 12 | Once(visitor.map_visit(self.0)) 13 | } 14 | } 15 | 16 | impl VisitLen for Once { 17 | fn len(&self) -> usize { 18 | 1 19 | } 20 | } 21 | 22 | impl Visit for Once 23 | where 24 | V: Visitor, 25 | { 26 | fn visit(&self, visitor: &mut V) -> Result<()> { 27 | visitor.visit(&self.0) 28 | } 29 | } 30 | 31 | impl VisitMut for Once 32 | where 33 | V: VisitorMut, 34 | { 35 | fn visit_mut(&mut self, visitor: &mut V) -> Result<()> { 36 | visitor.visit_mut(&mut self.0) 37 | } 38 | } 39 | 40 | impl UpdateWith> for Once 41 | where 42 | T: UpdateWith, 43 | { 44 | fn create_with(updater: Once) -> Self { 45 | Once(T::create_with(updater.0)) 46 | } 47 | 48 | fn update_with(&mut self, updater: Once, equality_matters: bool) -> bool { 49 | self.0.update_with(updater.0, equality_matters) && equality_matters 50 | } 51 | } 52 | 53 | impl SpecificUpdate for Once 54 | where 55 | T: SpecificUpdate, 56 | T::UpdateTo: UpdateWith, 57 | { 58 | type UpdateTo = Once; 59 | } 60 | -------------------------------------------------------------------------------- /irisia/src/structure/slot.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{RefCell, RefMut}; 2 | use std::rc::Rc; 3 | 4 | use crate::dom::RenderMultiple; 5 | use crate::{dom::EMUpdateContent, update_with::SpecificUpdate}; 6 | 7 | use super::{MapVisit, UpdateWith}; 8 | 9 | pub struct Slot(pub Rc>); 10 | 11 | impl MapVisit> for &Slot { 12 | type Output = Self; 13 | fn map(self, _: &EMUpdateContent) -> Self { 14 | self 15 | } 16 | } 17 | 18 | impl UpdateWith<&Slot> for Slot { 19 | fn create_with(updater: &Slot) -> Self { 20 | Slot(updater.0.clone()) 21 | } 22 | 23 | fn update_with(&mut self, updater: &Slot, equality_matters: bool) -> bool { 24 | if Rc::ptr_eq(&self.0, &updater.0) { 25 | self.0 = updater.0.clone(); 26 | false 27 | } else { 28 | equality_matters 29 | } 30 | } 31 | } 32 | 33 | impl Slot { 34 | pub fn new(value: T) -> Self { 35 | Slot(Rc::new(RefCell::new(value))) 36 | } 37 | 38 | pub fn borrow_mut(&self) -> RefMut { 39 | self.0.borrow_mut() 40 | } 41 | } 42 | 43 | impl RenderMultiple for Slot 44 | where 45 | T: RenderMultiple, 46 | { 47 | fn render( 48 | &self, 49 | lr: &mut crate::dom::layer::LayerRebuilder, 50 | interval: std::time::Duration, 51 | ) -> crate::Result<()> { 52 | self.0.borrow().render(lr, interval) 53 | } 54 | 55 | fn emit_event(&self, npe: &crate::application::event_comp::NewPointerEvent) -> bool { 56 | self.0.borrow().emit_event(npe) 57 | } 58 | 59 | fn layout( 60 | &self, 61 | f: &mut dyn FnMut( 62 | &dyn crate::style::style_box::InsideStyleBox, 63 | ) -> Option, 64 | ) -> crate::Result<()> { 65 | self.0.borrow().layout(f) 66 | } 67 | 68 | fn len(&self) -> usize { 69 | self.0.borrow().len() 70 | } 71 | 72 | fn peek_styles(&self, f: &mut dyn FnMut(&dyn crate::style::style_box::InsideStyleBox)) { 73 | self.0.borrow().peek_styles(f) 74 | } 75 | 76 | fn as_any(&mut self) -> &mut dyn std::any::Any { 77 | self 78 | } 79 | } 80 | 81 | impl SpecificUpdate for &Slot { 82 | type UpdateTo = Slot; 83 | } 84 | -------------------------------------------------------------------------------- /irisia/src/style/branch.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use super::{style_box::InsideStyleBox, StyleContainer}; 4 | 5 | #[derive(Clone)] 6 | pub enum Branch { 7 | ArmA(T), 8 | ArmB(U), 9 | } 10 | 11 | impl InsideStyleBox for Branch 12 | where 13 | T: StyleContainer, 14 | U: StyleContainer, 15 | { 16 | fn get_style_raw(&self, empty_option: &mut dyn Any) -> bool { 17 | match self { 18 | Self::ArmA(v) => v.get_style_raw(empty_option), 19 | Self::ArmB(v) => v.get_style_raw(empty_option), 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /irisia/src/style/chain.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use super::{style_box::InsideStyleBox, StyleContainer}; 4 | 5 | #[derive(Clone)] 6 | pub struct Chain { 7 | pub former: A, 8 | pub latter: B, 9 | } 10 | 11 | impl InsideStyleBox for Chain 12 | where 13 | A: StyleContainer, 14 | B: StyleContainer, 15 | { 16 | fn get_style_raw(&self, empty_option: &mut dyn Any) -> bool { 17 | self.former.get_style_raw(empty_option) || self.latter.get_style_raw(empty_option) 18 | } 19 | } 20 | 21 | impl Chain 22 | where 23 | A: StyleContainer, 24 | B: StyleContainer, 25 | { 26 | pub fn new(f: A, l: B) -> Self { 27 | Chain { 28 | former: f, 29 | latter: l, 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /irisia/src/style/mod.rs: -------------------------------------------------------------------------------- 1 | //! ## 样式格式 2 | //! 对于下列代码 3 | //! ```no_run 4 | //! irisia::style!{ 5 | //! box_shadow: 6 | //! 10px, 10px, 7 | //! .outset, 8 | //! .blur 20px, 9 | //! .color Color::SKYBLUE, 10 | //! .pass_tuple ("bar", 7px) 11 | //! ; 12 | //! } 13 | //! ``` 14 | //! 1. **`box_shadow`**: 15 | //! 将会转换成类型储存在样式集结构体中,并在初始化时执行`StyleBoxShadow::style_create((Pixel(10), Pixel(10)))` 16 | //! 17 | //! 2. **`10px`**: 18 | //! `px`, `pct`等后缀属于框架内置的固定数字表示法。`10px`将转换成`irisia::Pixel(10)` 19 | //! 20 | //! 3. **`.outset`**: 21 | //! 以`.`开头且无参数,`style.outset()` 22 | //! 23 | //! 4. **`.blur 20px`**: 24 | //! 以`.`开头有参数,`style.blur(Pixel(20))`,至多只允许一个参数 25 | //! 26 | 27 | pub mod branch; 28 | pub mod chain; 29 | pub mod once; 30 | pub mod reader; 31 | pub(crate) mod style_box; 32 | 33 | use self::style_box::InsideStyleBox; 34 | pub use self::{branch::Branch, chain::Chain, once::Once, style_box::StyleBox}; 35 | 36 | use crate::{self as irisia, primitive::Pixel, Style as DeriveStyle}; 37 | use irisia_backend::skia_safe::Color; 38 | 39 | pub use reader::StyleReader; 40 | 41 | pub trait Style: Clone + 'static {} 42 | 43 | #[derive(Debug, DeriveStyle, Clone, Copy, PartialEq)] 44 | #[style(from)] 45 | pub struct StyleColor(pub Color); 46 | 47 | #[derive(Debug, DeriveStyle, Clone, Copy, PartialEq)] 48 | pub enum XAxisBound { 49 | #[style(option)] 50 | Left(#[style(default)] Pixel), 51 | 52 | #[style(option)] 53 | Right(#[style(default)] Pixel), 54 | } 55 | 56 | pub trait StyleContainer: InsideStyleBox { 57 | fn get_style(&self) -> Option; 58 | 59 | fn read(&self) -> S 60 | where 61 | S: StyleReader, 62 | { 63 | S::read_style(self) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /irisia/src/style/once.rs: -------------------------------------------------------------------------------- 1 | use crate::Style; 2 | 3 | use super::style_box::InsideStyleBox; 4 | 5 | #[derive(Clone)] 6 | pub struct Once(pub T); 7 | 8 | impl InsideStyleBox for Once 9 | where 10 | T: Style, 11 | { 12 | fn get_style_raw(&self, empty_option: &mut dyn std::any::Any) -> bool { 13 | if let Some(this) = empty_option.downcast_mut::>() { 14 | *this = Some(self.0.clone()); 15 | true 16 | } else { 17 | false 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /irisia/src/style/reader.rs: -------------------------------------------------------------------------------- 1 | use super::{Style, StyleContainer}; 2 | 3 | pub trait StyleReader { 4 | fn read_style(container: impl StyleContainer) -> Self; 5 | } 6 | 7 | impl StyleReader for Option 8 | where 9 | T: Style, 10 | { 11 | fn read_style(container: impl StyleContainer) -> Self { 12 | container.get_style() 13 | } 14 | } 15 | 16 | impl StyleReader for T 17 | where 18 | T: Style + Default, 19 | { 20 | fn read_style(container: impl StyleContainer) -> Self { 21 | container.get_style().unwrap_or_default() 22 | } 23 | } 24 | 25 | #[cfg(doc)] 26 | impl StyleReader for (T,) 27 | where 28 | T: StyleReader, 29 | { 30 | fn read_style(container: &impl StyleContainer) -> Self { 31 | (T::read_style(container),) 32 | } 33 | } 34 | 35 | macro_rules! impl_reader { 36 | ($($($T:ident)*,)*) => { 37 | $( 38 | #[cfg(not(doc))] 39 | impl<$($T,)*> StyleReader for ($($T,)*) 40 | where 41 | $($T: StyleReader,)* 42 | { 43 | #[allow(clippy::unused_unit)] 44 | fn read_style(_container: impl StyleContainer) -> Self { 45 | ($($T::read_style(&_container),)*) 46 | } 47 | } 48 | )* 49 | }; 50 | } 51 | 52 | impl_reader! { 53 | , 54 | A, 55 | A B, 56 | A B C, 57 | A B C D, 58 | A B C D E, 59 | A B C D E F, 60 | A B C D E F G, 61 | A B C D E F G H, 62 | A B C D E F G H I, 63 | A B C D E F G H I J, 64 | A B C D E F G H I J K, 65 | A B C D E F G H I J K L, 66 | } 67 | 68 | #[macro_export] 69 | macro_rules! read_style { 70 | ($container: expr => { 71 | $($name:ident: $Style: ty,)* 72 | })=>{ 73 | let ($($name,)*) = { 74 | let style_container = $container; 75 | ($(<$Style as $crate::style::reader::StyleReader>::read_style(style_container),)*) 76 | }; 77 | }; 78 | 79 | ($binding:ident in $container: expr => { 80 | $($name:ident: $Style: ty,)* 81 | }) => { 82 | let $binding = { 83 | struct __IrisiaAnonymousStyleReader { 84 | $($name: $Style,)* 85 | } 86 | 87 | let style_container = $container; 88 | 89 | __IrisiaAnonymousStyleReader { 90 | $($name: <$Style as $crate::style::reader::StyleReader>::read_style(style_container),)* 91 | } 92 | }; 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /irisia/src/style/style_box.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::Style; 4 | 5 | use super::StyleContainer; 6 | 7 | pub trait InsideStyleBox { 8 | fn get_style_raw(&self, empty_option: &mut dyn Any) -> bool; 9 | } 10 | 11 | impl InsideStyleBox for () { 12 | fn get_style_raw(&self, _empty_option: &mut dyn Any) -> bool { 13 | false 14 | } 15 | } 16 | 17 | impl InsideStyleBox for &T 18 | where 19 | T: InsideStyleBox + ?Sized, 20 | { 21 | fn get_style_raw(&self, empty_option: &mut dyn Any) -> bool { 22 | (**self).get_style_raw(empty_option) 23 | } 24 | } 25 | 26 | impl StyleContainer for T 27 | where 28 | T: InsideStyleBox + ?Sized, 29 | { 30 | fn get_style(&self) -> Option { 31 | let mut option: Option = None; 32 | self.get_style_raw(&mut option); 33 | option 34 | } 35 | } 36 | 37 | pub struct StyleBox(Box); 38 | 39 | impl InsideStyleBox for StyleBox { 40 | fn get_style_raw(&self, empty_option: &mut dyn Any) -> bool { 41 | self.0.get_style_raw(empty_option) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /irisia/src/update_with.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{OsStr, OsString}, 3 | path::{Path, PathBuf}, 4 | }; 5 | 6 | use smallvec::{Array, SmallVec}; 7 | 8 | /// Custom update methods. 9 | pub trait UpdateWith: Sized { 10 | /// Update the state, returns whether 11 | /// the new state is equivalent to the previous. 12 | /// 13 | /// - `updater`: The new state. 14 | /// - `equality_matters`: Whether the return value is matters. 15 | /// If not, there is no overhead returning `false` directly 16 | /// without checking the equality. 17 | /// - `return`: Update result, default be `bool`. Indicates whether 18 | /// `Self` is updated, or what fields of `Self` are updated. 19 | fn update_with(&mut self, updater: T, equality_matters: bool) -> bool; 20 | fn create_with(updater: T) -> Self; 21 | } 22 | 23 | pub trait SpecificUpdate { 24 | type UpdateTo; 25 | 26 | fn to_created(self) -> Self::UpdateTo 27 | where 28 | Self: Sized, 29 | Self::UpdateTo: UpdateWith, 30 | { 31 | Self::UpdateTo::create_with(self) 32 | } 33 | } 34 | 35 | // for String/OsString/PathBuf from str/OsStr/Path 36 | 37 | macro_rules! impl_as_ref { 38 | ($Struct:ident $slice:ident $push:ident) => { 39 | impl UpdateWith for $Struct 40 | where 41 | T: AsRef<$slice>, 42 | { 43 | fn update_with(&mut self, updater: T, equality_matters: bool) -> bool { 44 | let unchanged = equality_matters && (self == updater.as_ref()); 45 | self.clear(); 46 | self.$push(updater.as_ref()); 47 | unchanged 48 | } 49 | 50 | fn create_with(updater: T) -> Self { 51 | updater.as_ref().into() 52 | } 53 | } 54 | }; 55 | } 56 | 57 | impl_as_ref!(String str push_str); 58 | impl_as_ref!(OsString OsStr push); 59 | impl_as_ref!(PathBuf Path push); 60 | 61 | // for Vec from iterator 62 | 63 | macro_rules! impl_vec { 64 | ($Vec:ty, $I: ident) => { 65 | fn update_with(&mut self, mut updater: $I, equality_matters: bool) -> bool { 66 | if equality_matters { 67 | let mut result = true; 68 | let mut old_elements = self.iter_mut(); 69 | 70 | for (old, new) in (&mut old_elements).zip(&mut updater) { 71 | result = result && (*old == new); 72 | *old = new; 73 | } 74 | 75 | if old_elements.len() != 0 { 76 | result = false; 77 | let len = old_elements.len(); 78 | self.drain(self.len() - len..); 79 | } 80 | 81 | if let Some(next) = updater.next() { 82 | result = false; 83 | self.push(next); 84 | self.extend(updater); 85 | } 86 | 87 | result 88 | } else { 89 | self.clear(); 90 | self.extend(updater); 91 | false 92 | } 93 | } 94 | 95 | fn create_with(updater: $I) -> Self { 96 | updater.collect() 97 | } 98 | }; 99 | } 100 | 101 | impl UpdateWith for Vec 102 | where 103 | I: Iterator, 104 | I::Item: PartialEq + 'static, 105 | { 106 | impl_vec!(Vec, I); 107 | } 108 | 109 | impl UpdateWith for SmallVec 110 | where 111 | A: Array, 112 | A::Item: PartialEq + 'static, 113 | I: Iterator, 114 | { 115 | impl_vec!(SmallVec, I); 116 | } 117 | 118 | // for Box from T's updater 119 | 120 | impl UpdateWith for Box 121 | where 122 | T: UpdateWith + ?Sized, 123 | { 124 | fn update_with(&mut self, updater: U, equality_matters: bool) -> bool { 125 | (**self).update_with(updater, equality_matters) 126 | } 127 | 128 | fn create_with(updater: U) -> Self { 129 | Box::new(T::create_with(updater)) 130 | } 131 | } 132 | --------------------------------------------------------------------------------