From da90654d7a8511288eb8163a768ef376c2981da7 Mon Sep 17 00:00:00 2001 From: "Austin M. Reppert" Date: Thu, 26 Mar 2026 02:57:07 -0400 Subject: [PATCH 1/3] Start website --- Cargo.lock | 19 ++ Cargo.toml | 1 + crates/craft_retained/src/lib.rs | 1 + website/Cargo.toml | 54 ++++++ ...24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg | Bin 0 -> 285 bytes ...24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg | Bin 0 -> 101 bytes ...24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg | Bin 0 -> 364 bytes ...24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg | Bin 0 -> 117 bytes ...24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg | Bin 0 -> 133 bytes website/index.html | 40 ++++ website/src/index.rs | 175 ++++++++++++++++++ website/src/main.rs | 103 +++++++++++ website/src/navbar.rs | 62 +++++++ website/src/page_wrapper.rs | 36 ++++ website/src/router.rs | 0 website/src/theme.rs | 33 ++++ website/src/web_link.rs | 25 +++ website/wasm-build.sh | 12 ++ 18 files changed, 561 insertions(+) create mode 100644 website/Cargo.toml create mode 100644 website/assets/brush_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg create mode 100644 website/assets/code_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg create mode 100644 website/assets/devices_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg create mode 100644 website/assets/electric_bolt_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg create mode 100644 website/assets/view_comfy_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg create mode 100644 website/index.html create mode 100644 website/src/index.rs create mode 100644 website/src/main.rs create mode 100644 website/src/navbar.rs create mode 100644 website/src/page_wrapper.rs create mode 100644 website/src/router.rs create mode 100644 website/src/theme.rs create mode 100644 website/src/web_link.rs create mode 100644 website/wasm-build.sh diff --git a/Cargo.lock b/Cargo.lock index 0a05221f..5ed88dea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3909,6 +3909,8 @@ dependencies = [ "rustls", "rustls-pki-types", "rustls-platform-verifier", + "serde", + "serde_json", "sync_wrapper", "tokio", "tokio-native-tls", @@ -5470,6 +5472,23 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "website" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "craft_retained", + "open", + "reqwest 0.13.2", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", + "tracing-web", + "util", + "web-sys", +] + [[package]] name = "weezl" version = "0.1.12" diff --git a/Cargo.toml b/Cargo.toml index 8dad7f12..435fdb17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "crates/craft_resource_manager", "crates/craft_runtime", "crates/craft_undo", + "website", ] [workspace.package] diff --git a/crates/craft_retained/src/lib.rs b/crates/craft_retained/src/lib.rs index 76b32bec..844b406e 100644 --- a/crates/craft_retained/src/lib.rs +++ b/crates/craft_retained/src/lib.rs @@ -13,6 +13,7 @@ pub use image; #[cfg(target_os = "android")] pub use winit::platform::android::activity::*; pub use winit::window::{Cursor, CursorIcon, WindowAttributes}; +pub use winit::window::Window as WinitWindow; pub use crate::craftcallback::CraftCallback; pub use crate::options::CraftOptions; diff --git a/website/Cargo.toml b/website/Cargo.toml new file mode 100644 index 00000000..0d897a2d --- /dev/null +++ b/website/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "website" +version = "0.1.0" +edition = "2024" + +[dependencies] + +tracing-subscriber = "0.3.19" +tracing = "0.1.41" + +util = { path = "../examples/util" } + +serde = { version = "1.0.213", features = ["derive"] } +serde_json = "1.0.133" +web-sys = { version = "0.3.77", features = ["Window", "Location", "History"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies.craft_retained] +path = "../crates/craft_retained" +default-features = false +features = [ + "vello_hybrid_renderer", + "vello_hybrid_renderer", + "http_client", + "png", + "jpeg", + "accesskit", + "system_fonts", + "markdown" +] + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.craft_retained] +path = "../crates/craft_retained" +default-features = false +features = [ + "vello_renderer", + "http_client", + "png", + "jpeg", + "accesskit", + "system_fonts", + "markdown" +] + +[target.'cfg(target_arch = "wasm32")'.dependencies] +tracing-web = "0.1.3" +console_error_panic_hook = "0.1.7" + +[dependencies.reqwest] +workspace = true +default-features = false +features = ["rustls", "json"] + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +open = "5" \ No newline at end of file diff --git a/website/assets/brush_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg b/website/assets/brush_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg new file mode 100644 index 0000000000000000000000000000000000000000..d037aba6273f7cfa697df83f8bc430eed1511ba6 GIT binary patch literal 285 zcmY*UQA)!=5PhYPDIvjPF-fB>5lboli0n^5A!#Y5#vO_!c!VB6FR&Nz0(*d7py${N zcz|5M3po4fzzoBiH}B1Sehlt2WEkLIm})pg7>*#Lj?4sIe&OEf9NIlb&}2hsj(=YX z;tR~J;FQS;cS%dr9b=Ja@d32B|x!41O;V&uxG|6*e!N literal 0 HcmV?d00001 diff --git a/website/assets/code_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg b/website/assets/code_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg new file mode 100644 index 0000000000000000000000000000000000000000..d982515b7c2bd86e39d0c79a2f0af7a07ba5d9e4 GIT binary patch literal 101 zcmXR4W7LvhkYHeBKmiT*%!~|dYzztv9t;c|3UCEle9#@taegSXp BHlhFk literal 0 HcmV?d00001 diff --git a/website/assets/electric_bolt_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg b/website/assets/electric_bolt_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg new file mode 100644 index 0000000000000000000000000000000000000000..cb71bf7b43245a297fdfd99c874f27126e7c643b GIT binary patch literal 117 zcmY+4K?*=X000NIIe35reLh!#micIm~6|+?*mL#Nme- yJr`d<#14`k0$LdGVYX_>V5NZ6F5f3~y^Ji=Q*q=*a+P<&6>Ej*jK)@hLJ3}wT@MEU literal 0 HcmV?d00001 diff --git a/website/assets/view_comfy_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg b/website/assets/view_comfy_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg new file mode 100644 index 0000000000000000000000000000000000000000..c517937804f27335830af5b88e94413ee7f76950 GIT binary patch literal 133 zcmY+4Ar62r5Co?wxgaYmk%Uy@v6WQ~@8AVIo$XbzVv?QRr-vhx3H + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/website/src/index.rs b/website/src/index.rs new file mode 100644 index 00000000..876dc04e --- /dev/null +++ b/website/src/index.rs @@ -0,0 +1,175 @@ +use std::sync::Arc; + +use craft_retained::elements::{Container, Element, Text}; +use craft_retained::style::{AlignItems, Display, FlexDirection, FlexWrap, FontWeight, JustifyContent, Overflow, Unit}; +use craft_retained::{Color, ResourceIdentifier, WinitWindow, palette, pct, px, rgb}; + +use crate::theme::{MOBILE_MEDIA_QUERY_WIDTH, WRAPPER_PADDING_LEFT, WRAPPER_PADDING_RIGHT, wrapper}; +use crate::web_link::WebLink; + +fn hero_intro() -> Container { + let bg_wrapper = Container::new().width(pct(100)).background_color(rgb(45, 48, 53)); + + let inner_wrapper = wrapper() + .display(Display::Flex) + .flex_direction(FlexDirection::Column) + .padding( + Unit::Px(100.0), + WRAPPER_PADDING_RIGHT, + Unit::Px(100.0), + WRAPPER_PADDING_LEFT, + ); + + let inner_wrapper = inner_wrapper + .push( + Text::new("A Reactive GUI Framework for Rust") + .color(Color::WHITE) + /*.font_size(if window_ctx.inner_size().width <= MOBILE_MEDIA_QUERY_WIDTH { + 36.0 + } else { + 56.0 + })*/ + .font_size(56.0) + .line_height(1.0) + .max_width(px(680)) + .font_weight(FontWeight::BOLD) + .margin(px(0), px(0), px(32), px(0)), + ) + .push( + Text::new("Build your UI with regular Rust code.") + .line_height(1.0) + .color(Color::WHITE) + .font_size(20.0), + ); + + let github_button = Text::new("GitHub") + .display(Display::Flex) + .align_items(Some(AlignItems::Center)) + .justify_content(Some(JustifyContent::Center)) + .font_size(22.0) + .min_width(px(100)) + .border_width(px(1), px(1), px(1), px(1)) + .border_radius((8.0, 8.0), (8.0, 8.0), (8.0, 8.0), (8.0, 8.0)) + .padding(px(8), px(20), px(8), px(20)) + .border_color( + palette::css::WHITE, + palette::css::WHITE, + palette::css::WHITE, + palette::css::WHITE, + ) + .color(palette::css::WHITE); + + let craft_button = Text::new("Learn Craft") + .display(Display::Flex) + .align_items(Some(AlignItems::Center)) + .justify_content(Some(JustifyContent::Center)) + .font_size(22.0) + .min_width(px(100)) + .border_radius((8.0, 8.0), (8.0, 8.0), (8.0, 8.0), (8.0, 8.0)) + .padding(px(8), px(20), px(8), px(20)) + .background_color(rgb(69, 117, 230)) + .color(palette::css::WHITE); + + let buttons = Container::new() + .display(Display::Flex) + .wrap(FlexWrap::Wrap) + .gap(px(17), px(17)) + .margin(px(40), px(0), px(0), px(0)) + //.push( + // Link::component() + // .props(Props::new(LinkProps { + // href: "/docs".to_string(), + // })) + // .push(craft_button) + //) + .push(WebLink("https://github.com/craft-gui/craft").push(github_button)); + + let inner_wrapper = inner_wrapper.push(buttons); + + bg_wrapper.push(inner_wrapper) +} + +fn hero_features() -> Container { + fn hero_item(title: &str, text: &str, icon: ResourceIdentifier) -> Container { + let sub_title_color = Color::from_rgb8(70, 70, 70); + + //let icon_title = Container::new() + // .push(TinyVg::new(icon)) + // .push(Text::new(title).font_weight(Weight::MEDIUM).font_size(24.0).margin(0, 0, 0, 10)); + + Container::new() + .gap(px(10), px(10)) + .flex_grow(1.0) + .flex_shrink(1.0) + .min_width(px(320)) + .flex_basis(pct(50)) + .display(Display::Flex) + .flex_direction(FlexDirection::Column) + //.push(icon_title) + .push(Text::new(text).font_size(18.0).color(sub_title_color)) + } + + Container::new() + .background_color(rgb(247, 247, 247)) + .width(pct(100)) + .push( + wrapper() + .padding(Unit::Px(100.0), WRAPPER_PADDING_LEFT, Unit::Px(100.0), WRAPPER_PADDING_RIGHT) + .display(Display::Flex) + .wrap(FlexWrap::Wrap) + .gap(px(0), px(50)) + .push(Text::new("Features").width(pct(100)).font_size(36.0).font_weight(FontWeight::SEMIBOLD)) + .push( + hero_item( + "Reactive", + "When your data changes, we automatically re-run your view function.", + ResourceIdentifier::Bytes(include_bytes!("../assets/electric_bolt_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg")) + ) + ) + .push( + hero_item( + "Components", + "Components are reusable blocks that manage their own state and define both how they are rendered and how they respond to updates.", + ResourceIdentifier::Bytes(include_bytes!("../assets/view_comfy_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg")) + ) + ) + .push( + hero_item( + "Pure Rust without macros", + "No macros.", + ResourceIdentifier::Bytes(include_bytes!("../assets/code_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg")) + ) + ) + .push( + hero_item( + "Web-like styling", + "We use Taffy, an implementation of the CSS flexbox, block, and grid layout algorithms, for simple and familiar styling.", + ResourceIdentifier::Bytes(include_bytes!("../assets/brush_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg")) + ) + ) + .push( + hero_item( + "Cross Platform", + "Currently we support Windows, macOS, Linux, Web, and Android.", + ResourceIdentifier::Bytes(include_bytes!("../assets/devices_24dp_000000_FILL0_wght400_GRAD0_opsz24.tvg")) + ) + ) + + ) +} + +pub(crate) fn index_page() -> Container { + Container::new() + .width(pct(100)) + .overflow(Overflow::Visible, Overflow::Scroll) + .push( + Container::new() + .display(Display::Flex) + .width(pct(100)) + .margin(Unit::Px(0.0), Unit::Auto, Unit::Px(0.0), Unit::Auto) + .flex_direction(FlexDirection::Column) + .flex_grow(1.0) + .push(hero_intro()) + .push(hero_features()), + ) +} diff --git a/website/src/main.rs b/website/src/main.rs new file mode 100644 index 00000000..df641210 --- /dev/null +++ b/website/src/main.rs @@ -0,0 +1,103 @@ +use std::cell::RefCell; +use std::ops::Deref; +use std::rc::Rc; + +use craft_retained::elements::{Container, Element, Window}; +use craft_retained::{CraftOptions, craft_main, pct}; + +use crate::page_wrapper::PageWrapper; + +mod navbar; +mod page_wrapper; +mod router; +mod theme; +mod index; +mod web_link; + +pub(crate) struct WebsiteGlobalState { + /// The current route that we are viewing. + route: String, +} + +impl WebsiteGlobalState { + pub(crate) fn get_route(&self) -> String { + #[cfg(target_arch = "wasm32")] + let path: String; + #[cfg(target_arch = "wasm32")] + { + let window = web_sys::window().expect("No window available."); + path = window + .location() + .pathname() + .map(|s| { + let trimmed_path = s.trim_end_matches('/'); + if trimmed_path.is_empty() { + "/".to_string() + } else { + trimmed_path.to_string() + } + }) + .unwrap_or("/".to_string()); + } + #[cfg(not(target_arch = "wasm32"))] + let path = self.route.clone(); + path + } + + pub(crate) fn set_route(&mut self, route: &str) { + self.route = route.to_string(); + + #[cfg(target_arch = "wasm32")] + { + let window = web_sys::window().unwrap(); + let history = window.history().unwrap(); + + history + .push_state_with_url(&web_sys::wasm_bindgen::JsValue::NULL, "", Some(route)) + .unwrap(); + } + } + + pub fn load_route(&mut self) { + #[cfg(not(target_arch = "wasm32"))] + { + // NOTE: In Git Bash, use `cargo run -- //examples`. + let route = std::env::args().nth(1).unwrap_or_else(|| "/".to_string()); + self.set_route(route.as_str()); + } + } +} + +impl Default for WebsiteGlobalState { + fn default() -> Self { + WebsiteGlobalState { + route: "/".to_string(), + } + } +} + +fn main() { + let options = CraftOptions { + ..Default::default() + }; + + #[allow(unused_mut)] + let mut global_state = Rc::new(RefCell::new(WebsiteGlobalState::default())); + + util::setup_logging(); + + let mut page_wrapper = PageWrapper::new(); + + let page = match global_state.borrow().route.as_str() { + "/" => { + index::index_page() + } + _ => { + index::index_page() + } + }; + + page_wrapper.set_content(page); + + craft_main(options); +} diff --git a/website/src/navbar.rs b/website/src/navbar.rs new file mode 100644 index 00000000..2e9e0331 --- /dev/null +++ b/website/src/navbar.rs @@ -0,0 +1,62 @@ +use craft_retained::elements::{Container, Element, Text}; +use craft_retained::style::{AlignItems, Display, JustifyContent, Unit, FontWeight}; +use craft_retained::{pct, px, rgb}; + +use crate::theme::{wrapper, NAVBAR_BACKGROUND_COLOR, NAVBAR_TEXT_COLOR, NAVBAR_TEXT_HOVERED_COLOR}; + +pub const NAVBAR_HEIGHT: f32 = 60.0; + +fn create_link(label: &str, route: &str) -> Text { + Text::new(label) + .id(format!("route_{route}").as_str()) + .margin(px(0), px(12), px(0), px(0)) + .font_size(16.0) + .selectable(false) + .color(NAVBAR_TEXT_COLOR) + /*.hovered() + .color(NAVBAR_TEXT_HOVERED_COLOR) + .underline(1.0, Color::BLACK, None) + .margin(px(0), "12px", px(0), px(0)) + .font_size(16.5) + .disable_selection() + .normal()*/ +} + +pub fn navbar() -> Container { + let border_color = rgb(240, 240, 240); + let container = Container::new() + .width(pct(100)) + .height(Unit::Px(NAVBAR_HEIGHT)) + .min_height(Unit::Px(NAVBAR_HEIGHT)) + .max_height(Unit::Px(NAVBAR_HEIGHT)) + .border_width(px(0), px(0), px(2), px(0)) + .border_color(border_color, border_color, border_color, border_color) + .background_color(NAVBAR_BACKGROUND_COLOR); + + let wrapper = wrapper() + .display(Display::Flex) + .justify_content(Some(JustifyContent::SpaceBetween)) + .align_items(Some(AlignItems::Center)) + // Left + .push( + Container::new() + .display(Display::Flex) + .justify_content(Some(JustifyContent::Center)) + .align_items(Some(AlignItems::Center)) + .push( + create_link("Craft", "/") + .font_size(32.0) + .font_weight(FontWeight::BOLD) + .margin(px(0), px(24), px(0), px(0)) + /*.hovered() + .font_size(32.0) + .font_weight(Weight::BOLD) + .margin(px(0), "24px", px(0), px(0)),*/ + ) + .push(create_link("Home", "/").margin(px(0), px(12), px(0), px(0))) + .push(create_link("Docs", "/docs").margin(px(0), px(12), px(0), px(0))) + .push(create_link("Examples", "/examples").margin(px(0), px(12), px(0), px(0))) + ); + + container.push(wrapper) +} diff --git a/website/src/page_wrapper.rs b/website/src/page_wrapper.rs new file mode 100644 index 00000000..f865356f --- /dev/null +++ b/website/src/page_wrapper.rs @@ -0,0 +1,36 @@ +use std::sync::Arc; +use craft_retained::elements::{Container, Element, Window}; +use craft_retained::pct; +use craft_retained::style::{Display, FlexDirection}; + +use crate::navbar::navbar; +use crate::theme::BODY_BACKGROUND_COLOR; + +pub struct PageWrapper { + root: Window, +} + +impl PageWrapper { + pub fn new() -> Self { + Self { + root: Window::new("Craft Gui") + .display(Display::Flex) + .flex_direction(FlexDirection::Column) + .width(pct(100)) + .height(pct(100)) + .push(navbar()) + .background_color(BODY_BACKGROUND_COLOR), + } + } + + pub fn set_content(&mut self, container: Container) { + if let Some(current_content) = self.root.get_children().get(1) { + self.root.remove_child(current_content.clone()).expect("Failed to remove child"); + } + self.root.clone().push(container); + } + + pub fn window(&self) -> Arc { + self.root.inner.borrow().winit_window().expect("No widow") + } +} diff --git a/website/src/router.rs b/website/src/router.rs new file mode 100644 index 00000000..e69de29b diff --git a/website/src/theme.rs b/website/src/theme.rs new file mode 100644 index 00000000..d94a2ab7 --- /dev/null +++ b/website/src/theme.rs @@ -0,0 +1,33 @@ +use craft_retained::elements::{Container, Element}; +use craft_retained::style::Unit; +use craft_retained::{Color, pct}; + +pub(crate) const BODY_BACKGROUND_COLOR: Color = Color::from_rgb8(255, 255, 255); + +pub(crate) const NAVBAR_BACKGROUND_COLOR: Color = Color::from_rgb8(255, 255, 255); +pub(crate) const NAVBAR_TEXT_COLOR: Color = Color::from_rgb8(50, 50, 50); +pub(crate) const NAVBAR_TEXT_HOVERED_COLOR: Color = Color::from_rgb8(0, 0, 0); + +pub(crate) const ACTIVE_LINK_COLOR: Color = Color::from_rgb8(42, 108, 200); +pub(crate) const DEFAULT_LINK_COLOR: Color = Color::from_rgb8(102, 102, 102); + +pub(crate) const WRAPPER_MAX_WIDTH: Unit = Unit::Px(1300.0); +pub(crate) const WRAPPER_MARGIN_LEFT: Unit = Unit::Auto; +pub(crate) const WRAPPER_MARGIN_RIGHT: Unit = Unit::Auto; +pub(crate) const WRAPPER_PADDING_LEFT: Unit = Unit::Px(20.0); +pub(crate) const WRAPPER_PADDING_RIGHT: Unit = Unit::Px(20.0); + +pub(crate) const MOBILE_MEDIA_QUERY_WIDTH: u32 = 850; + +pub(crate) fn wrapper() -> Container { + Container::new() + .margin(Unit::Px(0.0), WRAPPER_MARGIN_RIGHT, Unit::Px(0.0), WRAPPER_MARGIN_LEFT) + .padding( + Unit::Px(0.0), + WRAPPER_PADDING_RIGHT, + Unit::Px(0.0), + WRAPPER_PADDING_LEFT, + ) + .width(pct(100)) + .max_width(WRAPPER_MAX_WIDTH) +} diff --git a/website/src/web_link.rs b/website/src/web_link.rs new file mode 100644 index 00000000..2d7b78b7 --- /dev/null +++ b/website/src/web_link.rs @@ -0,0 +1,25 @@ +use std::rc::Rc; + +use craft_retained::elements::{Container, Element}; +use craft_retained::events::ui_events::pointer::PointerButton; + +pub fn WebLink(href: &str) -> Container { + let href = href.to_string(); + + Container::new().on_pointer_button_up(Rc::new(move |_event, pointer_button_event| { + if pointer_button_event.button == Some(PointerButton::Primary) { + #[cfg(target_arch = "wasm32")] + { + if let Some(win) = web_sys::window() { + // Use the captured owned string + let _ = win.open_with_url(&href); + } + } + + #[cfg(not(target_arch = "wasm32"))] + { + open::that(&href).unwrap(); + } + } + })) +} diff --git a/website/wasm-build.sh b/website/wasm-build.sh new file mode 100644 index 00000000..07fb3318 --- /dev/null +++ b/website/wasm-build.sh @@ -0,0 +1,12 @@ +set -e + + +#rustup target add wasm32-unknown-unknown +#cargo install -f wasm-bindgen-cli +#cargo install simple-http-server + +cargo build --target wasm32-unknown-unknown --release + +wasm-bindgen ../target/wasm32-unknown-unknown/release/website.wasm --target web --no-typescript --out-dir dist --out-name website +cp index.html dist/index.html +simple-http-server dist -c wasm,html,js --try-file dist/index.html -i --coep --coop --ip 0.0.0.0 \ No newline at end of file From 0e59977404413de6e15901bfad0bcdd2109ddc46 Mon Sep 17 00:00:00 2001 From: "Austin M. Reppert" Date: Sat, 28 Mar 2026 12:38:15 -0400 Subject: [PATCH 2/3] Improve behavior when removing nodes. --- .../craft_retained/src/elements/container.rs | 6 ++ .../craft_retained/src/elements/dropdown.rs | 23 +++++- crates/craft_retained/src/elements/image.rs | 6 ++ .../src/elements/slider/slider_element.rs | 6 ++ crates/craft_retained/src/elements/text.rs | 6 ++ .../src/elements/text_input/mod.rs | 6 ++ .../src/elements/traits/element_internals.rs | 50 +++++++++---- crates/craft_retained/src/elements/window.rs | 6 ++ .../craft_retained/src/layout/taffy_tree.rs | 21 +++++- crates/craft_retained/src/lib.rs | 3 +- website/src/docs.rs | 0 website/src/link.rs | 25 +++++++ website/src/page_wrapper.rs | 36 ---------- website/src/router.rs | 71 +++++++++++++++++++ 14 files changed, 207 insertions(+), 58 deletions(-) create mode 100644 website/src/docs.rs create mode 100644 website/src/link.rs delete mode 100644 website/src/page_wrapper.rs diff --git a/crates/craft_retained/src/elements/container.rs b/crates/craft_retained/src/elements/container.rs index ab6fa18c..ac5e0967 100644 --- a/crates/craft_retained/src/elements/container.rs +++ b/crates/craft_retained/src/elements/container.rs @@ -36,6 +36,12 @@ impl Default for Container { impl Element for Container {} +impl Drop for ContainerInner { + fn drop(&mut self) { + ElementInternals::drop(self) + } +} + impl AsElement for Container { fn as_element_rc(&self) -> Rc> { self.inner.clone() diff --git a/crates/craft_retained/src/elements/dropdown.rs b/crates/craft_retained/src/elements/dropdown.rs index b810a871..4aab5daf 100644 --- a/crates/craft_retained/src/elements/dropdown.rs +++ b/crates/craft_retained/src/elements/dropdown.rs @@ -80,6 +80,12 @@ impl Default for Dropdown { impl Element for Dropdown {} +impl Drop for DropdownInner { + fn drop(&mut self) { + ElementInternals::drop(self) + } +} + impl AsElement for Dropdown { fn as_element_rc(&self) -> Rc> { self.inner.clone() @@ -582,7 +588,7 @@ impl DropdownInner { // Remove the old selected element from the layout tree. if let Some(old_selected_element) = &self.selected_element { TAFFY_TREE.with_borrow_mut(|taffy_tree| { - taffy_tree.remove_subtree(old_selected_element.borrow().element_data().layout.taffy_node_id()); + taffy_tree.unparent_node(old_selected_element.borrow().element_data().layout.taffy_node_id()); }); } @@ -609,7 +615,12 @@ impl DropdownInner { }); } - fn update_most_recently_hovered_child(&mut self, message: &EventKind, list_box: Rectangle, list_scroll_box: Rectangle) { + fn update_most_recently_hovered_child( + &mut self, + message: &EventKind, + list_box: Rectangle, + list_scroll_box: Rectangle, + ) { if let EventKind::PointerMovedEvent(pb) = message { let pointer_position = Point::new(pb.current.position.x, pb.current.position.y); let is_pointer_in_list = list_box.contains(&pointer_position); @@ -656,7 +667,13 @@ impl DropdownInner { } } - fn handle_child_click(&mut self, event: &mut Event, pointer_position: &Point, is_pointer_in_window: bool, is_pointer_in_scrollbar: bool) { + fn handle_child_click( + &mut self, + event: &mut Event, + pointer_position: &Point, + is_pointer_in_window: bool, + is_pointer_in_scrollbar: bool, + ) { if is_pointer_in_window && !is_pointer_in_scrollbar { let mut should_hide_window = false; for (child_index, child) in self.children().iter().cloned().enumerate() { diff --git a/crates/craft_retained/src/elements/image.rs b/crates/craft_retained/src/elements/image.rs index 19de6ad2..8a573206 100644 --- a/crates/craft_retained/src/elements/image.rs +++ b/crates/craft_retained/src/elements/image.rs @@ -47,6 +47,12 @@ impl crate::elements::ElementData for ImageInner { impl Element for Image {} +impl Drop for ImageInner { + fn drop(&mut self) { + ElementInternals::drop(self) + } +} + impl AsElement for Image { fn as_element_rc(&self) -> Rc> { self.inner.clone() diff --git a/crates/craft_retained/src/elements/slider/slider_element.rs b/crates/craft_retained/src/elements/slider/slider_element.rs index da28520f..d06b1caa 100644 --- a/crates/craft_retained/src/elements/slider/slider_element.rs +++ b/crates/craft_retained/src/elements/slider/slider_element.rs @@ -305,6 +305,12 @@ impl SliderInner { impl Element for Slider {} +impl Drop for SliderInner { + fn drop(&mut self) { + ElementInternals::drop(self) + } +} + impl AsElement for Slider { fn as_element_rc(&self) -> Rc> { self.inner.clone() diff --git a/crates/craft_retained/src/elements/text.rs b/crates/craft_retained/src/elements/text.rs index b3475dc7..56f7cc0e 100644 --- a/crates/craft_retained/src/elements/text.rs +++ b/crates/craft_retained/src/elements/text.rs @@ -90,6 +90,12 @@ pub struct TextState { impl Element for Text {} +impl Drop for TextInner { + fn drop(&mut self) { + ElementInternals::drop(self) + } +} + impl AsElement for Text { fn as_element_rc(&self) -> Rc> { self.inner.clone() diff --git a/crates/craft_retained/src/elements/text_input/mod.rs b/crates/craft_retained/src/elements/text_input/mod.rs index b2646c65..bfec1463 100644 --- a/crates/craft_retained/src/elements/text_input/mod.rs +++ b/crates/craft_retained/src/elements/text_input/mod.rs @@ -135,6 +135,12 @@ impl TextInput { impl Element for TextInput {} +impl Drop for TextInputInner { + fn drop(&mut self) { + ElementInternals::drop(self) + } +} + impl AsElement for TextInput { fn as_element_rc(&self) -> Rc> { self.inner.clone() diff --git a/crates/craft_retained/src/elements/traits/element_internals.rs b/crates/craft_retained/src/elements/traits/element_internals.rs index 0029f722..a6991e39 100644 --- a/crates/craft_retained/src/elements/traits/element_internals.rs +++ b/crates/craft_retained/src/elements/traits/element_internals.rs @@ -14,7 +14,7 @@ use craft_renderer::RenderList; use crate::app::{ELEMENTS, FOCUS, TAFFY_TREE}; use crate::elements::scrollable::{ScrollState, draw_scrollbar}; -use crate::elements::{ElementData, ElementIdMap, ScrollOptions, WindowInternal}; +use crate::elements::{ElementData, ScrollOptions, WindowInternal}; use crate::events::pointer_capture::PointerCapture; use crate::events::{DropdownItemSelectedHandler, Event, EventKind, KeyboardInputHandler, PointerCaptureHandler, PointerEnterHandler, PointerEventHandler, PointerLeaveHandler, PointerUpdateHandler, ScrollHandler, SliderValueChangedHandler}; use crate::layout::TaffyTree; @@ -23,7 +23,10 @@ use crate::text::text_context::TextContext; use crate::{Color, CraftError}; /// Internal element methods that should typically be ignored by users. Public for custom elements. -pub trait ElementInternals: ElementData + Any { +/// +/// Drop is required to clean up any taffy nodes allocated by the element. +#[allow(drop_bounds)] +pub trait ElementInternals: ElementData + Any + Drop { fn deep_clone(&self) -> Rc>; fn position_in_parent(&self) -> Option { @@ -444,14 +447,14 @@ pub trait ElementInternals: ElementData + Any { // Remove the parent reference. child.borrow_mut().element_data_mut().parent = None; - child.borrow_mut().element_data_mut().window = None; + //child.borrow_mut().element_data_mut().window = None; child.borrow_mut().propagate_window_down(); TAFFY_TREE.with_borrow_mut(|taffy_tree| { let child_id = child.borrow().element_data().layout.taffy_node_id; if let Some(child_id) = child_id { - taffy_tree.remove_subtree(child_id); + taffy_tree.unparent_node(child_id); } let parent_id = self.element_data().layout.taffy_node_id; @@ -459,21 +462,14 @@ pub trait ElementInternals: ElementData + Any { }); // TODO: Move to document - fn remove_element_from_document( - node: Rc>, - pointer_capture: &mut PointerCapture, - elements: &mut ElementIdMap, - ) { - elements.remove_id(node.borrow().element_data().internal_id); + fn remove_element_from_document(node: Rc>, pointer_capture: &mut PointerCapture) { pointer_capture.remove_element(&node); for child in node.borrow().children() { - remove_element_from_document(child.clone(), pointer_capture, elements); + remove_element_from_document(child.clone(), pointer_capture); } } - ELEMENTS.with_borrow_mut(|elements| { - remove_element_from_document(child.clone(), &mut self.pointer_capture().borrow_mut(), elements); - }); + remove_element_from_document(child.clone(), &mut self.pointer_capture().borrow_mut()); child.borrow_mut().unfocus(); @@ -938,6 +934,32 @@ pub trait ElementInternals: ElementData + Any { .winit_window .clone() } + + /// Recursively prints the IDs of this element and all of its descendants. + fn print_tree_ids(&self, depth: usize) { + let indent = " ".repeat(depth); + + // Access the ID from element_data. + // If it's None, we can print "Unnamed Element" or the internal_id. + let id_label = self.element_data().internal_id.to_string(); + + println!("{}└─ {}: {}", indent, id_label, self.element_data().window.is_some()); + + for child in self.children() { + child.borrow().print_tree_ids(depth + 1); + } + } + + fn drop(&mut self) { + if let Some(taffy_node) = self.element_data().layout.taffy_node_id { + TAFFY_TREE.with_borrow_mut(|taffy_tree| { + taffy_tree.remove_node(taffy_node); + }); + } + ELEMENTS.with_borrow_mut(|elements| { + elements.remove_id(self.element_data().internal_id); + }); + } } pub fn resolve_clip_for_scrollable(element: &mut dyn ElementInternals, clip_bounds: Option) { diff --git a/crates/craft_retained/src/elements/window.rs b/crates/craft_retained/src/elements/window.rs index 1129c730..42ffab8c 100644 --- a/crates/craft_retained/src/elements/window.rs +++ b/crates/craft_retained/src/elements/window.rs @@ -110,6 +110,12 @@ impl Default for Window { impl Element for Window {} +impl Drop for WindowInternal { + fn drop(&mut self) { + ElementInternals::drop(self) + } +} + impl AsElement for Window { fn as_element_rc(&self) -> Rc> { self.inner.clone() diff --git a/crates/craft_retained/src/layout/taffy_tree.rs b/crates/craft_retained/src/layout/taffy_tree.rs index 9f6d7192..5790e918 100644 --- a/crates/craft_retained/src/layout/taffy_tree.rs +++ b/crates/craft_retained/src/layout/taffy_tree.rs @@ -105,7 +105,7 @@ impl TaffyTree { self.is_layout_dirty = false; } - /// Remove the entire layout subtree. + /// Remove a specific `node` and its ancestors from the tree and drop it pub fn remove_subtree(&mut self, node: NodeId) { // Can we avoid this allocation? let children = self.inner.children(node).unwrap(); @@ -113,8 +113,23 @@ impl TaffyTree { for child in children { self.remove_subtree(child); } + self.remove_node(node); + self.request_layout(); + } + + /// Removes the `node`. + /// + /// The `node` is not removed from the tree entirely, it is simply no longer attached to its previous parent. + pub fn unparent_node(&mut self, node: NodeId) { + if let Some(parent) = self.inner.parent(node) { + self.inner.remove_child(parent, node).unwrap(); + self.request_layout(); + } + } - self.inner.remove(node).map(|_| ()).unwrap(); + /// Remove a specific node from the tree and drop it + pub fn remove_node(&mut self, node: NodeId) { + self.inner.remove(node).unwrap(); self.request_layout(); } @@ -158,7 +173,7 @@ impl TaffyTree { #[inline(always)] pub fn request_layout(&mut self) { self.is_layout_dirty = true; - self.is_apply_layout_dirty = Vec::new(); + self.is_apply_layout_dirty.clear(); } #[inline(always)] diff --git a/crates/craft_retained/src/lib.rs b/crates/craft_retained/src/lib.rs index 844b406e..397ac743 100644 --- a/crates/craft_retained/src/lib.rs +++ b/crates/craft_retained/src/lib.rs @@ -12,8 +12,7 @@ pub use image; #[cfg(target_os = "android")] pub use winit::platform::android::activity::*; -pub use winit::window::{Cursor, CursorIcon, WindowAttributes}; -pub use winit::window::Window as WinitWindow; +pub use winit::window::{Cursor, CursorIcon, Window as WinitWindow, WindowAttributes}; pub use crate::craftcallback::CraftCallback; pub use crate::options::CraftOptions; diff --git a/website/src/docs.rs b/website/src/docs.rs new file mode 100644 index 00000000..e69de29b diff --git a/website/src/link.rs b/website/src/link.rs new file mode 100644 index 00000000..2d7b78b7 --- /dev/null +++ b/website/src/link.rs @@ -0,0 +1,25 @@ +use std::rc::Rc; + +use craft_retained::elements::{Container, Element}; +use craft_retained::events::ui_events::pointer::PointerButton; + +pub fn WebLink(href: &str) -> Container { + let href = href.to_string(); + + Container::new().on_pointer_button_up(Rc::new(move |_event, pointer_button_event| { + if pointer_button_event.button == Some(PointerButton::Primary) { + #[cfg(target_arch = "wasm32")] + { + if let Some(win) = web_sys::window() { + // Use the captured owned string + let _ = win.open_with_url(&href); + } + } + + #[cfg(not(target_arch = "wasm32"))] + { + open::that(&href).unwrap(); + } + } + })) +} diff --git a/website/src/page_wrapper.rs b/website/src/page_wrapper.rs deleted file mode 100644 index f865356f..00000000 --- a/website/src/page_wrapper.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::sync::Arc; -use craft_retained::elements::{Container, Element, Window}; -use craft_retained::pct; -use craft_retained::style::{Display, FlexDirection}; - -use crate::navbar::navbar; -use crate::theme::BODY_BACKGROUND_COLOR; - -pub struct PageWrapper { - root: Window, -} - -impl PageWrapper { - pub fn new() -> Self { - Self { - root: Window::new("Craft Gui") - .display(Display::Flex) - .flex_direction(FlexDirection::Column) - .width(pct(100)) - .height(pct(100)) - .push(navbar()) - .background_color(BODY_BACKGROUND_COLOR), - } - } - - pub fn set_content(&mut self, container: Container) { - if let Some(current_content) = self.root.get_children().get(1) { - self.root.remove_child(current_content.clone()).expect("Failed to remove child"); - } - self.root.clone().push(container); - } - - pub fn window(&self) -> Arc { - self.root.inner.borrow().winit_window().expect("No widow") - } -} diff --git a/website/src/router.rs b/website/src/router.rs index e69de29b..15da677a 100644 --- a/website/src/router.rs +++ b/website/src/router.rs @@ -0,0 +1,71 @@ +use std::cell::RefCell; +use std::rc::Rc; +use std::sync::Arc; +use craft_retained::elements::{Container, Element, Window}; +use craft_retained::pct; +use craft_retained::style::{Display, FlexDirection}; + +use crate::navbar::navbar; +use crate::theme::BODY_BACKGROUND_COLOR; +use crate::{docs, index, WebsiteGlobalState}; +use crate::docs::docs; +use crate::index::index_page; + +#[derive(Clone)] +pub struct Router { + pub root: Window, + global_state: Rc>, + index: Container, + docs: Container, +} + +pub type NavigateFn = Box; + +impl Router { + pub fn new(global_state: Rc>) -> Self { + let window = Window::new("Craft Gui") + .display(Display::Flex) + .flex_direction(FlexDirection::Column) + .width(pct(100)) + .height(pct(100)) + .push(navbar()) + .background_color(BODY_BACKGROUND_COLOR); + Self { + root: window.clone(), + index: index_page(Box::new(|path| { + println!("navigating"); + })), + docs: docs(Box::new(|path| { + + })), + global_state, + } + } + + pub fn set_content(&self, container: Container) { + if let Some(current_content) = self.root.get_children().get(1) { + self.root.remove_child(current_content.clone()).expect("Failed to remove child"); + } + self.root.clone().push(container); + } + + pub fn navigate(&self) { + let page = match self.global_state.borrow().route.as_str() { + "/" => { + self.index.clone() + } + "/docs" => { + self.docs.clone() + } + _ => { + self.index.clone() + } + }; + + self.set_content(page); + } + + pub fn window(&self) -> Arc { + self.root.inner.borrow().winit_window().expect("No widow") + } +} From 86f029ab7a03c611402f8fc16c25e3b7041449ee Mon Sep 17 00:00:00 2001 From: "Austin M. Reppert" Date: Sat, 28 Mar 2026 12:38:45 -0400 Subject: [PATCH 3/3] Work on navigation. --- website/src/docs.rs | 19 ++++++++++ website/src/index.rs | 36 ++++++++++--------- website/src/link.rs | 21 ++++------- website/src/main.rs | 27 +++++++------- website/src/navbar.rs | 61 +++++++++++++++++-------------- website/src/router.rs | 79 ++++++++++++++++++++++------------------- website/src/theme.rs | 8 ++--- website/src/web_link.rs | 1 + 8 files changed, 138 insertions(+), 114 deletions(-) diff --git a/website/src/docs.rs b/website/src/docs.rs index e69de29b..d23db73f 100644 --- a/website/src/docs.rs +++ b/website/src/docs.rs @@ -0,0 +1,19 @@ +use craft_retained::elements::{Container, Element}; +use craft_retained::pct; +use craft_retained::style::{Display, FlexDirection, Overflow, Unit}; + +use crate::router::NavigateFn; + +pub(crate) fn docs(_navigate_fn: NavigateFn) -> Container { + Container::new() + .width(pct(100)) + .overflow(Overflow::Visible, Overflow::Scroll) + .push( + Container::new() + .display(Display::Flex) + .width(pct(100)) + .margin(Unit::Px(0.0), Unit::Auto, Unit::Px(0.0), Unit::Auto) + .flex_direction(FlexDirection::Column) + .flex_grow(1.0), + ) +} diff --git a/website/src/index.rs b/website/src/index.rs index 876dc04e..bc74c7d3 100644 --- a/website/src/index.rs +++ b/website/src/index.rs @@ -1,13 +1,15 @@ -use std::sync::Arc; - use craft_retained::elements::{Container, Element, Text}; use craft_retained::style::{AlignItems, Display, FlexDirection, FlexWrap, FontWeight, JustifyContent, Overflow, Unit}; use craft_retained::{Color, ResourceIdentifier, WinitWindow, palette, pct, px, rgb}; +use std::cell::RefCell; +use std::rc::Rc; -use crate::theme::{MOBILE_MEDIA_QUERY_WIDTH, WRAPPER_PADDING_LEFT, WRAPPER_PADDING_RIGHT, wrapper}; +use crate::link::Link; +use crate::router::{NavigateFn, Router}; +use crate::theme::{WRAPPER_PADDING_LEFT, WRAPPER_PADDING_RIGHT, wrapper}; use crate::web_link::WebLink; -fn hero_intro() -> Container { +fn hero_intro(navigate_fn: NavigateFn) -> Container { let bg_wrapper = Container::new().width(pct(100)).background_color(rgb(45, 48, 53)); let inner_wrapper = wrapper() @@ -60,6 +62,7 @@ fn hero_intro() -> Container { .color(palette::css::WHITE); let craft_button = Text::new("Learn Craft") + .id("xxx") .display(Display::Flex) .align_items(Some(AlignItems::Center)) .justify_content(Some(JustifyContent::Center)) @@ -75,13 +78,7 @@ fn hero_intro() -> Container { .wrap(FlexWrap::Wrap) .gap(px(17), px(17)) .margin(px(40), px(0), px(0), px(0)) - //.push( - // Link::component() - // .props(Props::new(LinkProps { - // href: "/docs".to_string(), - // })) - // .push(craft_button) - //) + .push(Link("/docs", move || navigate_fn("/docs")).push(craft_button)) .push(WebLink("https://github.com/craft-gui/craft").push(github_button)); let inner_wrapper = inner_wrapper.push(buttons); @@ -93,9 +90,14 @@ fn hero_features() -> Container { fn hero_item(title: &str, text: &str, icon: ResourceIdentifier) -> Container { let sub_title_color = Color::from_rgb8(70, 70, 70); - //let icon_title = Container::new() - // .push(TinyVg::new(icon)) - // .push(Text::new(title).font_weight(Weight::MEDIUM).font_size(24.0).margin(0, 0, 0, 10)); + let icon_title = Container::new() + // .push(TinyVg::new(icon)) + .push( + Text::new(title) + .font_weight(FontWeight::MEDIUM) + .font_size(24.0) + .margin(px(0), px(0), px(0), px(10)), + ); Container::new() .gap(px(10), px(10)) @@ -105,7 +107,7 @@ fn hero_features() -> Container { .flex_basis(pct(50)) .display(Display::Flex) .flex_direction(FlexDirection::Column) - //.push(icon_title) + .push(icon_title) .push(Text::new(text).font_size(18.0).color(sub_title_color)) } @@ -158,7 +160,7 @@ fn hero_features() -> Container { ) } -pub(crate) fn index_page() -> Container { +pub(crate) fn index_page(navigate_fn: NavigateFn) -> Container { Container::new() .width(pct(100)) .overflow(Overflow::Visible, Overflow::Scroll) @@ -169,7 +171,7 @@ pub(crate) fn index_page() -> Container { .margin(Unit::Px(0.0), Unit::Auto, Unit::Px(0.0), Unit::Auto) .flex_direction(FlexDirection::Column) .flex_grow(1.0) - .push(hero_intro()) + .push(hero_intro(navigate_fn)) .push(hero_features()), ) } diff --git a/website/src/link.rs b/website/src/link.rs index 2d7b78b7..46a27bf2 100644 --- a/website/src/link.rs +++ b/website/src/link.rs @@ -3,23 +3,16 @@ use std::rc::Rc; use craft_retained::elements::{Container, Element}; use craft_retained::events::ui_events::pointer::PointerButton; -pub fn WebLink(href: &str) -> Container { - let href = href.to_string(); +#[allow(non_snake_case)] +pub fn Link(href: &str, on_click: F) -> Container +where + F: Fn() + 'static, +{ + let on_click = Rc::new(on_click); Container::new().on_pointer_button_up(Rc::new(move |_event, pointer_button_event| { if pointer_button_event.button == Some(PointerButton::Primary) { - #[cfg(target_arch = "wasm32")] - { - if let Some(win) = web_sys::window() { - // Use the captured owned string - let _ = win.open_with_url(&href); - } - } - - #[cfg(not(target_arch = "wasm32"))] - { - open::that(&href).unwrap(); - } + on_click(); } })) } diff --git a/website/src/main.rs b/website/src/main.rs index df641210..204acafd 100644 --- a/website/src/main.rs +++ b/website/src/main.rs @@ -1,17 +1,17 @@ use std::cell::RefCell; -use std::ops::Deref; use std::rc::Rc; -use craft_retained::elements::{Container, Element, Window}; +use craft_retained::elements::{Container, Element, ElementInternals, Window}; use craft_retained::{CraftOptions, craft_main, pct}; -use crate::page_wrapper::PageWrapper; +use crate::router::Router; +mod docs; +mod index; +mod link; mod navbar; -mod page_wrapper; mod router; mod theme; -mod index; mod web_link; pub(crate) struct WebsiteGlobalState { @@ -86,18 +86,15 @@ fn main() { util::setup_logging(); - let mut page_wrapper = PageWrapper::new(); + global_state.borrow_mut().load_route(); - let page = match global_state.borrow().route.as_str() { - "/" => { - index::index_page() - } - _ => { - index::index_page() - } - }; + let page_wrapper = Router::new(global_state.clone()); + + /* let root = page_wrapper.borrow().root.clone(); - page_wrapper.set_content(page); + root.inner.borrow().print_tree_ids(4); + */ + page_wrapper.borrow().navigate(); craft_main(options); } diff --git a/website/src/navbar.rs b/website/src/navbar.rs index 2e9e0331..6ce616e5 100644 --- a/website/src/navbar.rs +++ b/website/src/navbar.rs @@ -1,28 +1,36 @@ +use crate::link::Link; +use crate::router::NavigateFn; +use crate::theme::{NAVBAR_BACKGROUND_COLOR, NAVBAR_TEXT_COLOR, wrapper}; use craft_retained::elements::{Container, Element, Text}; -use craft_retained::style::{AlignItems, Display, JustifyContent, Unit, FontWeight}; +use craft_retained::style::{AlignItems, Display, FontWeight, JustifyContent, Unit}; use craft_retained::{pct, px, rgb}; -use crate::theme::{wrapper, NAVBAR_BACKGROUND_COLOR, NAVBAR_TEXT_COLOR, NAVBAR_TEXT_HOVERED_COLOR}; - pub const NAVBAR_HEIGHT: f32 = 60.0; -fn create_link(label: &str, route: &str) -> Text { - Text::new(label) - .id(format!("route_{route}").as_str()) - .margin(px(0), px(12), px(0), px(0)) - .font_size(16.0) - .selectable(false) - .color(NAVBAR_TEXT_COLOR) - /*.hovered() - .color(NAVBAR_TEXT_HOVERED_COLOR) - .underline(1.0, Color::BLACK, None) - .margin(px(0), "12px", px(0), px(0)) - .font_size(16.5) - .disable_selection() - .normal()*/ +fn create_link(navigate_fn: NavigateFn, label: &str, route: &str) -> Container { + let route_owned = route.to_string(); + let nav = navigate_fn.clone(); + Link(route, move || { + nav(&route_owned); + }) + .push( + Text::new(label) + .id(format!("route_{route}").as_str()) + .margin(px(0), px(12), px(0), px(0)) + .font_size(16.0) + .selectable(false) + .color(NAVBAR_TEXT_COLOR), + ) + /*.hovered() + .color(NAVBAR_TEXT_HOVERED_COLOR) + .underline(1.0, Color::BLACK, None) + .margin(px(0), "12px", px(0), px(0)) + .font_size(16.5) + .disable_selection() + .normal()*/ } -pub fn navbar() -> Container { +pub fn navbar(navigate_fn: NavigateFn) -> Container { let border_color = rgb(240, 240, 240); let container = Container::new() .width(pct(100)) @@ -44,18 +52,17 @@ pub fn navbar() -> Container { .justify_content(Some(JustifyContent::Center)) .align_items(Some(AlignItems::Center)) .push( - create_link("Craft", "/") + create_link(navigate_fn.clone(), "Craft", "/") .font_size(32.0) .font_weight(FontWeight::BOLD) - .margin(px(0), px(24), px(0), px(0)) - /*.hovered() - .font_size(32.0) - .font_weight(Weight::BOLD) - .margin(px(0), "24px", px(0), px(0)),*/ + .margin(px(0), px(24), px(0), px(0)), /*.hovered() + .font_size(32.0) + .font_weight(Weight::BOLD) + .margin(px(0), "24px", px(0), px(0)),*/ ) - .push(create_link("Home", "/").margin(px(0), px(12), px(0), px(0))) - .push(create_link("Docs", "/docs").margin(px(0), px(12), px(0), px(0))) - .push(create_link("Examples", "/examples").margin(px(0), px(12), px(0), px(0))) + .push(create_link(navigate_fn.clone(), "Home", "/").margin(px(0), px(12), px(0), px(0))) + .push(create_link(navigate_fn.clone(), "Docs", "/docs").margin(px(0), px(12), px(0), px(0))) + .push(create_link(navigate_fn.clone(), "Examples", "/examples").margin(px(0), px(12), px(0), px(0))), ); container.push(wrapper) diff --git a/website/src/router.rs b/website/src/router.rs index 15da677a..f0f3dc44 100644 --- a/website/src/router.rs +++ b/website/src/router.rs @@ -1,15 +1,15 @@ -use std::cell::RefCell; -use std::rc::Rc; -use std::sync::Arc; use craft_retained::elements::{Container, Element, Window}; use craft_retained::pct; use craft_retained::style::{Display, FlexDirection}; +use std::cell::RefCell; +use std::rc::{Rc, Weak}; +use std::sync::Arc; -use crate::navbar::navbar; -use crate::theme::BODY_BACKGROUND_COLOR; -use crate::{docs, index, WebsiteGlobalState}; use crate::docs::docs; use crate::index::index_page; +use crate::navbar::navbar; +use crate::theme::BODY_BACKGROUND_COLOR; +use crate::{WebsiteGlobalState, docs, index}; #[derive(Clone)] pub struct Router { @@ -19,53 +19,58 @@ pub struct Router { docs: Container, } -pub type NavigateFn = Box; +pub type NavigateFn = Rc; impl Router { - pub fn new(global_state: Rc>) -> Self { - let window = Window::new("Craft Gui") - .display(Display::Flex) - .flex_direction(FlexDirection::Column) - .width(pct(100)) - .height(pct(100)) - .push(navbar()) - .background_color(BODY_BACKGROUND_COLOR); - Self { - root: window.clone(), - index: index_page(Box::new(|path| { - println!("navigating"); - })), - docs: docs(Box::new(|path| { + pub fn new(global_state: Rc>) -> Rc> { + let state = global_state.clone(); + Rc::new_cyclic(|me: &Weak>| { + let me = me.clone(); - })), - global_state, - } + let navigate_logic: NavigateFn = Rc::new(move |route| { + state.borrow_mut().set_route(route); + if let Some(router) = me.upgrade() { + router.borrow().navigate(); + } + }); + + let window = Window::new("Craft Gui") + .display(Display::Flex) + .flex_direction(FlexDirection::Column) + .width(pct(100)) + .height(pct(100)) + .push(navbar(navigate_logic.clone())) + .background_color(BODY_BACKGROUND_COLOR); + + RefCell::new(Self { + root: window.clone(), + index: index_page(navigate_logic.clone()), + docs: docs(navigate_logic.clone()), + global_state: global_state.clone(), + }) + }) } - pub fn set_content(&self, container: Container) { + fn set_content(&self, container: Container) { if let Some(current_content) = self.root.get_children().get(1) { - self.root.remove_child(current_content.clone()).expect("Failed to remove child"); + self.root + .remove_child(current_content.clone()) + .expect("Failed to remove child"); } self.root.clone().push(container); } pub fn navigate(&self) { let page = match self.global_state.borrow().route.as_str() { - "/" => { - self.index.clone() - } - "/docs" => { - self.docs.clone() - } - _ => { - self.index.clone() - } + "/" => self.index.clone(), + "/docs" => self.docs.clone(), + _ => self.index.clone(), }; self.set_content(page); } - pub fn window(&self) -> Arc { + /*pub fn window(&self) -> Arc { self.root.inner.borrow().winit_window().expect("No widow") - } + }*/ } diff --git a/website/src/theme.rs b/website/src/theme.rs index d94a2ab7..17456eb1 100644 --- a/website/src/theme.rs +++ b/website/src/theme.rs @@ -6,10 +6,10 @@ pub(crate) const BODY_BACKGROUND_COLOR: Color = Color::from_rgb8(255, 255, 255); pub(crate) const NAVBAR_BACKGROUND_COLOR: Color = Color::from_rgb8(255, 255, 255); pub(crate) const NAVBAR_TEXT_COLOR: Color = Color::from_rgb8(50, 50, 50); -pub(crate) const NAVBAR_TEXT_HOVERED_COLOR: Color = Color::from_rgb8(0, 0, 0); +//pub(crate) const NAVBAR_TEXT_HOVERED_COLOR: Color = Color::from_rgb8(0, 0, 0); -pub(crate) const ACTIVE_LINK_COLOR: Color = Color::from_rgb8(42, 108, 200); -pub(crate) const DEFAULT_LINK_COLOR: Color = Color::from_rgb8(102, 102, 102); +//pub(crate) const ACTIVE_LINK_COLOR: Color = Color::from_rgb8(42, 108, 200); +//pub(crate) const DEFAULT_LINK_COLOR: Color = Color::from_rgb8(102, 102, 102); pub(crate) const WRAPPER_MAX_WIDTH: Unit = Unit::Px(1300.0); pub(crate) const WRAPPER_MARGIN_LEFT: Unit = Unit::Auto; @@ -17,7 +17,7 @@ pub(crate) const WRAPPER_MARGIN_RIGHT: Unit = Unit::Auto; pub(crate) const WRAPPER_PADDING_LEFT: Unit = Unit::Px(20.0); pub(crate) const WRAPPER_PADDING_RIGHT: Unit = Unit::Px(20.0); -pub(crate) const MOBILE_MEDIA_QUERY_WIDTH: u32 = 850; +//pub(crate) const MOBILE_MEDIA_QUERY_WIDTH: u32 = 850; pub(crate) fn wrapper() -> Container { Container::new() diff --git a/website/src/web_link.rs b/website/src/web_link.rs index 2d7b78b7..564fbdc6 100644 --- a/website/src/web_link.rs +++ b/website/src/web_link.rs @@ -3,6 +3,7 @@ use std::rc::Rc; use craft_retained::elements::{Container, Element}; use craft_retained::events::ui_events::pointer::PointerButton; +#[allow(non_snake_case)] pub fn WebLink(href: &str) -> Container { let href = href.to_string();