diff --git a/README.md b/README.md index dbe75fd..0100bd6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +# Notice +This project is on indefinite hiatus for right now. I appreciate the Rust community's interest in GUI frameworks, but this project is personal for me - I worked on it extensively during a time when my younger brother was battling Leukemia, and so returning to it brings up a lot of things that I prefer to take time dealing with. + +If you're interested in following work I'm doing in the GUI space with regards to Rust, feel free to follow [appkit-rs](https://github.com/ryanmcgrath/appkit-rs), which would end up being one of the underlying layers of this anyway (much the same way that gtk-rs would need to back, well, Gtk). + Potion Alchemy - A Rust GUI Framework diff --git a/alchemy/Cargo.toml b/alchemy/Cargo.toml index 45a006c..a376ab6 100644 --- a/alchemy/Cargo.toml +++ b/alchemy/Cargo.toml @@ -13,13 +13,10 @@ keywords = ["gui", "css", "styles", "layout", "react"] maintenance = { status = "actively-developed" } [features] -default = [] cocoa = ["alchemy-cocoa", "alchemy-lifecycle/cocoa"] -gtkrs = ["alchemy-gtkrs", "alchemy-lifecycle/gtkrs"] [dependencies] alchemy-cocoa = { version = "0.1", path = "../cocoa", optional = true } -alchemy-gtkrs = { version = "0.1", path = "../gtk", optional = true } alchemy-lifecycle = { version = "0.1", path = "../lifecycle" } alchemy-macros = { version = "0.1", path = "../macros" } alchemy-styles = { version = "0.1", path = "../styles", features = ["parser"] } diff --git a/alchemy/src/app.rs b/alchemy/src/app.rs index be47cf7..f1230d2 100644 --- a/alchemy/src/app.rs +++ b/alchemy/src/app.rs @@ -1,49 +1,79 @@ //! This module implements the Application structure and associated -//! lifecycle methods. +//! lifecycle methods. You typically never create this struct yourself; +//! in Alchemy, there's a global `shared_app` that you should use to work +//! with the `App` struct. //! //! This ensures that you can respond to application lifecycles, and so //! routing things around works correctly. -use std::cell::RefCell; +use std::sync::{Arc, Mutex}; +use alchemy_styles::{StyleSheet, THEME_ENGINE}; use alchemy_lifecycle::traits::AppDelegate; +use crate::window::WindowManager; + #[cfg(feature = "cocoa")] pub use alchemy_cocoa::app::{App as PlatformAppBridge}; -#[cfg(feature = "gtkrs")] -pub use alchemy_gtkrs::app::{App as PlatformAppBridge}; +/// A default delegate that is mostly used for creating the initial struct, +/// without requiring the actual `AppDelegate` from the user. Will ideally +/// never see the light of day. +struct DefaultAppDelegate; +impl AppDelegate for DefaultAppDelegate {} /// The Application structure itself. It holds a Mutex'd platform bridge, to /// handle communicating with the platform-specific app instance, along with a -/// delegate to forward events to. +/// delegate to forward events to. The `ThemeEngine` and `WindowManager` are +/// also stored here for easy access. pub struct App { - pub bridge: Option>, - pub delegate: RefCell> + pub(crate) bridge: Mutex>, + pub delegate: Mutex>, + pub windows: WindowManager } impl App { /// Creates a new app, allocated on the heap. Provides a pointer to /// said allocated instance so that the platform-specific app instances /// can loop events back around. - pub fn new(state: S) -> Box { - let mut app = Box::new(App { - bridge: None, - delegate: RefCell::new(Box::new(state)) + pub(crate) fn new() -> Arc { + let app = Arc::new(App { + bridge: Mutex::new(None), + delegate: Mutex::new(Box::new(DefaultAppDelegate {})), + windows: WindowManager::new() }); let app_ptr: *const App = &*app; - app.bridge = Some(RefCell::new(PlatformAppBridge::new(app_ptr))); - + app.configure_bridge(app_ptr); app } + /// Handles providing the app pointer to the inner bridge. + pub(crate) fn configure_bridge(&self, ptr: *const App) { + let mut bridge = self.bridge.lock().unwrap(); + *bridge = Some(PlatformAppBridge::new(ptr)); + } + + /// Convenience method for registering one-off styles. Typically, you would want + /// to store your stylesheets as separate files, to enable hot-reloading - but it's + /// conceivable that you might want to just have them in your app, too, and this enables + /// that use case. + pub fn register_styles(&self, theme_key: &str, stylesheet: StyleSheet) { + THEME_ENGINE.register_styles(theme_key, stylesheet); + } + /// Runs the app instance, by setting the necessary delegate and forwarding the run call /// to the inner backing application. This is a blocking operation; if you run this, you /// will want to begin your app (for real) in `AppDelegate::did_finish_launching()`. - pub fn run(&self) { - if let Some(bridge) = &self.bridge { - bridge.borrow_mut().run(); + pub fn run(&self, state: S) { + { + let mut delegate = self.delegate.lock().unwrap(); + *delegate = Box::new(state); + } + + let lock = self.bridge.lock().unwrap(); + if let Some(bridge) = &*lock { + bridge.run(); } } } @@ -55,41 +85,41 @@ impl App { impl AppDelegate for App { /// Called when the application will finish launching. fn will_finish_launching(&mut self) { - let mut delegate = self.delegate.borrow_mut(); + let mut delegate = self.delegate.lock().unwrap(); delegate.will_finish_launching(); } /// Called when the application did finish launching. fn did_finish_launching(&mut self) { - let mut delegate = self.delegate.borrow_mut(); + let mut delegate = self.delegate.lock().unwrap(); delegate.did_finish_launching(); } /// Called when the application will become active. We can use this, for instance, /// to resume rendering cycles and so on. fn will_become_active(&mut self) { - let mut delegate = self.delegate.borrow_mut(); + let mut delegate = self.delegate.lock().unwrap(); delegate.will_become_active(); } /// Called when the application did become active. We can use this, for instance, /// to resume rendering cycles and so on. fn did_become_active(&mut self) { - let mut delegate = self.delegate.borrow_mut(); + let mut delegate = self.delegate.lock().unwrap(); delegate.did_become_active(); } /// Called when the application will resigned active. We can use this, for instance, /// to pause rendering cycles and so on. fn will_resign_active(&mut self) { - let mut delegate = self.delegate.borrow_mut(); + let mut delegate = self.delegate.lock().unwrap(); delegate.will_resign_active(); } /// Called when the application has resigned active. We can use this, for instance, /// to pause rendering cycles and so on. fn did_resign_active(&mut self) { - let mut delegate = self.delegate.borrow_mut(); + let mut delegate = self.delegate.lock().unwrap(); delegate.did_resign_active(); } @@ -97,13 +127,20 @@ impl AppDelegate for App { /// to avoid termination if Alchemy needs more time for something, /// for whatever reason. fn should_terminate(&self) -> bool { - let delegate = self.delegate.borrow_mut(); + let delegate = self.delegate.lock().unwrap(); delegate.should_terminate() } /// Called when the application is about to terminate. fn will_terminate(&mut self) { - let mut delegate = self.delegate.borrow_mut(); + let mut delegate = self.delegate.lock().unwrap(); delegate.will_terminate(); } + + /// This is a private method, and you should not attempt to call it or + /// rely on it. It exists to enable easy loopback of Window-level events + /// on some platforms. + fn _window_will_close(&self, window_id: usize) { + self.windows.will_close(window_id); + } } diff --git a/alchemy/src/components/text.rs b/alchemy/src/components/text.rs index cba0d8c..105a537 100644 --- a/alchemy/src/components/text.rs +++ b/alchemy/src/components/text.rs @@ -15,9 +15,6 @@ use alchemy_lifecycle::traits::{Component, Props, PlatformSpecificNodeType}; #[cfg(feature = "cocoa")] use alchemy_cocoa::text::{Text as PlatformTextBridge}; -#[cfg(feature = "gtkrs")] -use alchemy_gtkrs::text::{Text as PlatformTextBridge}; - pub struct TextProps; /// Text rendering is a complicated mess, and being able to defer to the diff --git a/alchemy/src/components/view.rs b/alchemy/src/components/view.rs index 6f0bb9c..a8ccdaf 100644 --- a/alchemy/src/components/view.rs +++ b/alchemy/src/components/view.rs @@ -17,9 +17,6 @@ use crate::components::Fragment; #[cfg(feature = "cocoa")] use alchemy_cocoa::view::{View as PlatformViewBridge}; -#[cfg(feature = "gtkrs")] -use alchemy_gtkrs::view::{View as PlatformViewBridge}; - pub struct ViewProps; /// Views are the most basic piece of the API. If you want to display something, you'll diff --git a/alchemy/src/lib.rs b/alchemy/src/lib.rs index efcbcef..3bf0bfd 100644 --- a/alchemy/src/lib.rs +++ b/alchemy/src/lib.rs @@ -5,6 +5,7 @@ //! CSS support (no cascading) provides a familiar syntax for developers who tend to work on //! UI/UX projects, and the Component lifecycle is familiar enough to anyone who's touched React. +use std::sync::Arc; pub use lazy_static::lazy_static; use proc_macro_hack::proc_macro_hack; @@ -28,10 +29,21 @@ pub use alchemy_macros::Props; pub use alchemy_styles::{Color, styles as style_attributes, SpacedSet, StyleSheet, StylesList}; mod app; -pub use app::App; +use app::App; pub mod components; pub use components::{Fragment, Text, View}; pub mod window; pub use window::Window; + +lazy_static! { + pub(crate) static ref SHARED_APP: Arc = App::new(); +} + +/// This function supports calling the shared global application instance from anywhere in your +/// code. It's useful in cases where you need to have an escape hatch, but if you're using it as +/// such, you may want to double check your Application design to make sure you need it. +pub fn shared_app() -> Arc { + SHARED_APP.clone() +} diff --git a/alchemy/src/window/manager.rs b/alchemy/src/window/manager.rs new file mode 100644 index 0000000..630c6e0 --- /dev/null +++ b/alchemy/src/window/manager.rs @@ -0,0 +1,60 @@ +//! Per-platform windows have their own nuances, and typically, their own windowservers. +//! We don't want to take away from that, but we do want to avoid scenarios where things get +//! a bit weird. +//! +//! Consider the following: let's say we have a `Window` instantiated in Rust, and we call +//! `.show()` on it. Then the window drops, on the Rust side. We should probably clean up our side, +//! right? +//! +//! There's also the fact that a user could opt to close a window. If that happens, we want to be +//! able to remove it from our structure... hence this manager that acts as a lightweight interface +//! for managing per-platform Window instances. + +use std::sync::{Arc, Mutex}; +use crate::window::AppWindow; + +/// A struct that provides a Window Manager, via some interior mutability magic. +pub struct WindowManager(Mutex>>>); + +impl WindowManager { + /// Creates a new WindowManager instance. + pub(crate) fn new() -> WindowManager { + WindowManager(Mutex::new(Vec::with_capacity(1))) + } + + /// Locks and acquires a new window ID, which our Windows use to loop back for + /// events and callbacks. + pub(crate) fn allocate_new_window_id(&self) -> usize { + let windows = self.0.lock().unwrap(); + windows.len() + 1 + } + + /// Adds an `AppWindow` to this instance. + pub(crate) fn add(&self, window: Arc>) { + let mut windows = self.0.lock().unwrap(); + if let None = windows.iter().position(|w| Arc::ptr_eq(&w, &window)) { + windows.push(window); + } + } + + /// On a `will_close` event, our delegates will loop back here and notify that a window + /// with x id is closing, and should be removed. The `WindowDelegate` `will_close()` event + /// is fired here. + /// + /// At the end of this, the window drops. + pub(crate) fn will_close(&self, window_id: usize) { + let mut windows = self.0.lock().unwrap(); + if let Some(index) = windows.iter().position(|window| { + let mut w = window.lock().unwrap(); + + if w.id == window_id { + w.delegate.will_close(); + return true; + } + + false + }) { + windows.remove(index); + } + } +} diff --git a/alchemy/src/window/mod.rs b/alchemy/src/window/mod.rs new file mode 100644 index 0000000..5cc5848 --- /dev/null +++ b/alchemy/src/window/mod.rs @@ -0,0 +1,7 @@ +//! This module implements Windows and their associated lifecycles. + +mod manager; +pub(crate) use manager::WindowManager; + +pub mod window; +pub use window::{AppWindow, Window}; diff --git a/alchemy/src/window.rs b/alchemy/src/window/window.rs similarity index 59% rename from alchemy/src/window.rs rename to alchemy/src/window/window.rs index 014d8c9..9c4a84b 100644 --- a/alchemy/src/window.rs +++ b/alchemy/src/window/window.rs @@ -1,8 +1,7 @@ //! Implements the Window API. It attempts to provide a nice, common interface across //! per-platform Window APIs. -use std::rc::Rc; -use std::cell::RefCell; +use std::sync::{Arc, Mutex}; use alchemy_lifecycle::{ComponentKey, RENDER_ENGINE}; use alchemy_lifecycle::rsx::RSX; @@ -10,21 +9,23 @@ use alchemy_lifecycle::traits::{Component, WindowDelegate}; use alchemy_styles::{Appearance, Style, StylesList, THEME_ENGINE}; +use crate::{App, SHARED_APP}; use crate::components::View; #[cfg(feature = "cocoa")] use alchemy_cocoa::window::{Window as PlatformWindowBridge}; -#[cfg(feature = "gtkrs")] -use alchemy_gtkrs::window::{Window as PlatformWindowBridge}; - +/// AppWindow contains the inner details of a Window. It's guarded by a Mutex on `Window`, +/// and you shouldn't create this yourself, but it's documented here so you can understand what +/// it holds. pub struct AppWindow { - pub styles: StylesList, + pub id: usize, + pub style_keys: StylesList, pub title: String, pub dimensions: (f64, f64, f64, f64), - pub bridge: Option, + pub bridge: PlatformWindowBridge, pub delegate: Box, - render_key: Option + pub render_key: ComponentKey } impl AppWindow { @@ -38,11 +39,9 @@ impl AppWindow { pub fn render(&mut self) { let mut style = Style::default(); let mut appearance = Appearance::default(); - THEME_ENGINE.configure_styles_for_keys(&self.styles, &mut style, &mut appearance); + THEME_ENGINE.configure_styles_for_keys(&self.style_keys, &mut style, &mut appearance); - if let Some(bridge) = &mut self.bridge { - bridge.apply_styles(&appearance); - } + self.bridge.apply_styles(&appearance); let children = match self.delegate.render() { Ok(opt) => opt, @@ -52,102 +51,90 @@ impl AppWindow { } }; - if let Some(render_key) = self.render_key { - match RENDER_ENGINE.diff_and_render_root(render_key, ( - self.dimensions.2, - self.dimensions.3 - ), children) { - Ok(_) => { } - Err(e) => { eprintln!("Error rendering window! {}", e); } - } + match RENDER_ENGINE.diff_and_render_root(self.render_key, ( + self.dimensions.2, + self.dimensions.3 + ), children) { + Ok(_) => { } + Err(e) => { eprintln!("Error rendering window! {}", e); } } } pub fn set_title(&mut self, title: &str) { self.title = title.into(); - if let Some(bridge) = &mut self.bridge { - bridge.set_title(title); - } + self.bridge.set_title(title); } pub fn set_dimensions(&mut self, x: f64, y: f64, width: f64, height: f64) { self.dimensions = (x, y, width, height); - if let Some(bridge) = &mut self.bridge { - bridge.set_dimensions(x, y, width, height); - } + self.bridge.set_dimensions(x, y, width, height); } /// Renders and calls through to the native platform window show method. pub fn show(&mut self) { self.render(); - if let Some(bridge) = &mut self.bridge { - bridge.show(); - } + self.bridge.show(); } /// Calls through to the native platform window close method. pub fn close(&mut self) { - if let Some(bridge) = &mut self.bridge { - bridge.close(); - } + self.bridge.close(); } } -impl WindowDelegate for AppWindow {} - -/// Window represents... well, a Window. When you create one, you get the Window back. -pub struct Window(pub Rc>); +/// Windows represented... well, a Window. When you create one, you get the Window back. When you +/// show one, a clone of the pointer is added to the window manager, and removed on close. +pub struct Window(pub(crate) Arc>); impl Window { /// Creates a new window. - pub fn new(delegate: S) -> Window { - let app_window = Rc::new(RefCell::new(AppWindow { - styles: "".into(), + pub fn new(delegate: S) -> Window { + let window_id = SHARED_APP.windows.allocate_new_window_id(); + let view = View::default(); + let shared_app_ptr: *const App = &**SHARED_APP; + + // This unwrap() is fine, since we implement View ourselves in Alchemy + let backing_node = view.borrow_native_backing_node().unwrap(); + let bridge = PlatformWindowBridge::new(window_id, backing_node, shared_app_ptr); + + let key = match RENDER_ENGINE.register_root_component(view) { + Ok(key) => key, + Err(_e) => { panic!("Uhhhh this really messed up"); } + }; + + Window(Arc::new(Mutex::new(AppWindow { + id: window_id, + style_keys: "".into(), title: "".into(), dimensions: (0., 0., 0., 0.), - bridge: None, + bridge: bridge, delegate: Box::new(delegate), - render_key: None - })); - - let app_ptr: *const RefCell = &*app_window; - { - // This unwrap() is fine, since we implement View ourselves in Alchemy - let view = View::default(); - let backing_node = view.borrow_native_backing_node().unwrap(); - - let mut window = app_window.borrow_mut(); - window.bridge = Some(PlatformWindowBridge::new(backing_node, app_ptr)); - window.render_key = match RENDER_ENGINE.register_root_component(view) { - Ok(key) => Some(key), - Err(_e) => { panic!("Uhhhh this really messed up"); } - }; - } - - Window(app_window) + render_key: key + }))) } /// Renders a window. By default, a window renders nothing - make sure you implement `render()` /// on your `WindowDelegate`. Note that calling `.show()` implicitly calls this for you, so you /// rarely need to call this yourself. pub fn render(&self) { - let window = self.0.borrow_mut(); + let mut window = self.0.lock().unwrap(); window.render(); } pub fn set_title(&self, title: &str) { - let mut window = self.0.borrow_mut(); + let mut window = self.0.lock().unwrap(); window.set_title(title); } pub fn set_dimensions(&mut self, x: f64, y: f64, width: f64, height: f64) { - let mut window = self.0.borrow_mut(); + let mut window = self.0.lock().unwrap(); window.set_dimensions(x, y, width, height); } /// Registers this window with the window manager, renders it, and shows it. pub fn show(&self) { - let mut window = self.0.borrow_mut(); + SHARED_APP.windows.add(self.0.clone()); + let mut window = self.0.lock().unwrap(); window.show(); } @@ -160,7 +147,9 @@ impl Window { /// Closes the window, unregistering it from the window manager in the process and ensuring the /// necessary delegate method(s) are fired. pub fn close(&self) { - let mut window = self.0.borrow_mut(); + let window_id = self.0.lock().unwrap().id; + SHARED_APP.windows.will_close(window_id); + let mut window = self.0.lock().unwrap(); window.close(); } } diff --git a/cocoa/src/window.rs b/cocoa/src/window.rs index 798b2a6..64b4dd9 100644 --- a/cocoa/src/window.rs +++ b/cocoa/src/window.rs @@ -2,8 +2,6 @@ //! Cocoa and associated widgets. This also handles looping back //! lifecycle events, such as window resizing or close events. -use std::cell::RefCell; - use std::sync::{Once, ONCE_INIT}; use cocoa::base::{id, nil, YES, NO}; @@ -15,10 +13,11 @@ use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; use objc::{msg_send, sel, sel_impl}; -use alchemy_lifecycle::traits::WindowDelegate; +use alchemy_lifecycle::traits::{AppDelegate, Component}; use alchemy_styles::Appearance; -static WINDOW_PTR: &str = "alchemyWindowPtr"; +static APP_PTR: &str = "alchemyAppPtr"; +static WINDOW_MANAGER_ID: &str = "alchemyWindowManagerID"; /// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime /// where our `NSWindow` and associated delegate live. @@ -31,7 +30,7 @@ impl Window { /// Creates a new `NSWindow` instance, configures it appropriately (e.g, titlebar appearance), /// injects an `NSObject` delegate wrapper, and retains the necessary Objective-C runtime /// pointers. - pub fn new(content_view: ShareId, window_ptr: *const RefCell) -> Window { + pub fn new(window_id: usize, content_view: ShareId, app_ptr: *const T) -> Window { let dimensions = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)); let style = NSWindowStyleMask::NSResizableWindowMask | @@ -46,13 +45,17 @@ impl Window { NO ).autorelease(); + msg_send![window, setTitlebarAppearsTransparent:YES]; + //msg_send![window, setTitleVisibility:1]; + // This is very important! NSWindow is an old class and has some behavior that we need // to disable, like... this. If we don't set this, we'll segfault entirely because the // Objective-C runtime gets out of sync. msg_send![window, setReleasedWhenClosed:NO]; - msg_send![window, setTitlebarAppearsTransparent:YES]; - msg_send![window, setContentView:content_view]; + //if let Some(view_ptr) = content_view.borrow_native_backing_node() { + msg_send![window, setContentView:content_view]; + //} ShareId::from_ptr(window) }; @@ -60,7 +63,8 @@ impl Window { let delegate = unsafe { let delegate_class = register_window_class::(); let delegate: id = msg_send![delegate_class, new]; - (&mut *delegate).set_ivar(WINDOW_PTR, window_ptr as usize); + (&mut *delegate).set_ivar(APP_PTR, app_ptr as usize); + (&mut *delegate).set_ivar(WINDOW_MANAGER_ID, window_id); msg_send![inner, setDelegate:delegate]; ShareId::from_ptr(delegate) }; @@ -132,25 +136,27 @@ impl Drop for Window { /// Called when a Window receives a `windowWillClose:` event. Loops back to the shared /// Alchemy app instance, so that our window manager can act appropriately. -extern fn will_close(this: &Object, _: Sel, _: id) { +extern fn will_close(this: &Object, _: Sel, _: id) { unsafe { - let window_ptr: usize = *this.get_ivar(WINDOW_PTR); - let window = window_ptr as *mut T; - (*window).will_close(); + let app_ptr: usize = *this.get_ivar(APP_PTR); + let window_id: usize = *this.get_ivar(WINDOW_MANAGER_ID); + let app = app_ptr as *mut T; + (*app)._window_will_close(window_id); }; } /// Injects an `NSObject` delegate subclass, with some callback and pointer ivars for what we /// need to do. -fn register_window_class() -> *const Class { +fn register_window_class() -> *const Class { static mut DELEGATE_CLASS: *const Class = 0 as *const Class; static INIT: Once = ONCE_INIT; INIT.call_once(|| unsafe { let superclass = Class::get("NSObject").unwrap(); - let mut decl = ClassDecl::new("AlchemyWindowDelegateShim", superclass).unwrap(); + let mut decl = ClassDecl::new("alchemyWindowDelegateShim", superclass).unwrap(); - decl.add_ivar::(WINDOW_PTR); + decl.add_ivar::(APP_PTR); + decl.add_ivar::(WINDOW_MANAGER_ID); decl.add_method(sel!(windowWillClose:), will_close:: as extern fn(&Object, _, _)); diff --git a/examples/layout/Cargo.toml b/examples/layout/Cargo.toml index 419c159..c3cc60e 100644 --- a/examples/layout/Cargo.toml +++ b/examples/layout/Cargo.toml @@ -5,4 +5,4 @@ authors = ["Ryan McGrath "] edition = "2018" [dependencies] -alchemy = { path = "../../alchemy", version = "0.2.0", features = ["gtkrs"] } +alchemy = { path = "../../alchemy", version = "0.2.0", features = ["cocoa"] } diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 0861b5c..2ea858f 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -8,22 +8,40 @@ /// @created March 26th, 2019 use alchemy::{ - App, AppDelegate, Error, rsx, - RSX, text, Text, View, Window, WindowDelegate + AppDelegate, Component, ComponentKey, Fragment, Error, Props, rsx, RSX, styles, text, + Text, View, Window, WindowDelegate }; pub struct AppState { - window: Option + window: Window } impl AppDelegate for AppState { fn did_finish_launching(&mut self) { - let mut window = Window::new(WindowState {}); - window.set_title("Layout Test"); - window.set_dimensions(100., 100., 600., 600.); - window.show(); - self.window = Some(window); - println!("Should be showing"); + self.window.set_title("Layout Test"); + self.window.set_dimensions(100., 100., 600., 600.); + self.window.show(); + } +} + +#[derive(Default)] +struct BannerProps {} + +#[derive(Props)] +struct Banner; + +impl Component for Banner { + fn new(_key: ComponentKey) -> Banner { + Banner {} + } + + fn render(&self, children: Vec) -> Result { + Ok(rsx! { + + + {children} + + }) } } @@ -36,7 +54,6 @@ impl WindowDelegate for WindowState { fn render(&self) -> Result { let messages = vec!["LOL"]; //, "wut", "BERT"]; - Ok(rsx! { "Hello there, my name is Bert" @@ -44,13 +61,64 @@ impl WindowDelegate for WindowState { {messages.iter().map(|message| rsx! { {text!("{}", message)} })} + + + + + }) } } fn main() { - App::new(AppState { - window: None - }).run() + let app = alchemy::shared_app(); + + app.register_styles("default", styles! { + root { background-color: #000; } + + LOL { + background-color: #307ace; + width: 500; + height: 230; + padding-top: 20; + padding-left: 20; + padding-right: 40; + } + + message { width: 500; height: 100; background-color: yellow; color: black; } + text { width: 500; height: 100; background-color: blue; color: white; } + + boxxx { + background-color: rgba(245, 217, 28, .8); + width: 100; + height: 100; + margin-top: 40; + margin-right: 20; + } + + box1 { + background-color: #f51c69; + width: 250; + height: 100; + } + + wut1 { + background-color: black; + width: 50; + height: 230; + } + + innermostBox { + background-color: green; + width: 20; + height: 20; + } + }); + + app.run(AppState { + window: Window::new(WindowState { + + }) + }); } diff --git a/gtk/Cargo.toml b/gtk/Cargo.toml deleted file mode 100644 index cff6464..0000000 --- a/gtk/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "alchemy-gtkrs" -description = "GTK bindings for Alchemy, a cross-platform GUI framework written in Rust." -version = "0.1.0" -edition = "2018" -authors = ["Ryan McGrath "] -license = "MPL-2.0+" -repository = "https://github.com/ryanmcgrath/alchemy" -categories = ["gui", "rendering::engine", "multimedia"] -keywords = ["gui", "gtk", "react"] - -[badges] -maintenance = { status = "actively-developed" } - -[dependencies] -alchemy-lifecycle = { version = "0.1", path = "../lifecycle", features = ["gtkrs"] } -alchemy-styles = { version = "0.1", path = "../styles" } -gtk = { version = "0.6.0", features = ["v3_16"] } -gio = "0.6.0" diff --git a/gtk/src/app.rs b/gtk/src/app.rs deleted file mode 100644 index 8f09e1f..0000000 --- a/gtk/src/app.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! A wrapper for `Application` on GTK-based systems. If you opt in to the `gtkrs` feature on -//! Alchemy, this will loop system-level application events back to your `AppDelegate`. - -use std::env; - -use gio::{ApplicationFlags}; -use gtk::{Application}; -use gio::prelude::{ApplicationExt, ApplicationExtManual}; - -use alchemy_lifecycle::traits::AppDelegate; - -/// A wrapper for `gtk::Application`. -pub struct App { - pub inner: Application -} - -impl App { - /// Creates a `gtk::Application` instance, and wires up appropriate lifecycle handlers to loop - /// back to the `AppDelegate`. - pub fn new(parent_app_ptr: *const T) -> Self { - let inner = Application::new("lol.my.app", ApplicationFlags::FLAGS_NONE) - .expect("Could not create GTK Application instance!"); - - inner.connect_activate(move |app| { - println!("ACTIVATED"); - let app = parent_app_ptr as *mut T; - unsafe { - (*app).did_finish_launching(); - } - println!("HELLO"); - }); - - println!("MADE"); - App { - inner: inner - } - } - - /// Kicks off the Run Loop for the Application instance. This blocks when called. - pub fn run(&self) { - println!("RUNNING"); - self.inner.run(&env::args().collect::>()); - } -} diff --git a/gtk/src/lib.rs b/gtk/src/lib.rs deleted file mode 100644 index 645916b..0000000 --- a/gtk/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! This crate provides a GTK backend for Alchemy, the Rust GUI framework. -//! This means that, on GTK-based systems, you'll be using native views -//! and other assorted controls. Where possible, it attempts to opt into -//! smoother rendering paths. -//! -//! # License -//! -//! Copyright 2019 Ryan McGrath. See the license files included in the root repository -//! for more information, along with credit to applicable parties for who this project -//! would not have happened. -//! -//! # Code of Conduct -//! -//! Please note that this project is released with a [Contributor Code of -//! Conduct][coc]. By participating in this project you agree to abide by its terms. -//! -//! [coc]: https://www.contributor-covenant.org/version/1/4/code-of-conduct - -pub mod app; -pub mod text; -pub mod view; -pub mod window; diff --git a/gtk/src/text.rs b/gtk/src/text.rs deleted file mode 100644 index c6e11b0..0000000 --- a/gtk/src/text.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! This wraps NTextField on macOS, and configures it to act like a label -//! with standard behavior that most users would expect. - -use alchemy_styles::{Color, Layout, Appearance}; - -use alchemy_lifecycle::traits::PlatformSpecificNodeType; - -static ALCHEMY_DELEGATE: &str = "alchemyDelegate"; - -/// A wrapper for `NSText`. This holds retained pointers for the Objective-C -/// runtime - namely, the view itself, and associated things such as background -/// colors and so forth. -#[derive(Debug)] -pub struct Text { -} - -impl Text { - /// Allocates a new `NSTextField` on the Objective-C side, ensuring that things like coordinate - /// flipping occur (macOS still uses (0,0) as lower-left by default), and opting in to layer - /// backed views for smoother scrolling. - pub fn new() -> Text { - Text { - } - } - - /// Returns a pointer to the underlying Objective-C view. The pointer is not mutable; however, - /// you can send messages to it (unsafely). - pub fn borrow_native_backing_node(&self) -> PlatformSpecificNodeType { - () - } - - /// Appends a child NSText (or subclassed type) to this view. - pub fn append_child(&mut self, child: PlatformSpecificNodeType) { - } - - /// Given a `&Style`, will set the frame, background color, borders and so forth. It then - /// calls `setNeedsDisplay:YES` on the Objective-C side, so that Cocoa will re-render this - /// view. - pub fn apply_styles(&mut self, appearance: &Appearance, layout: &Layout) { - } - - pub fn set_text(&mut self, text: String) { - } - - pub fn render(&mut self) { - } -} diff --git a/gtk/src/view.rs b/gtk/src/view.rs deleted file mode 100644 index 915b054..0000000 --- a/gtk/src/view.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Implements a View Component struct. The most common -//! basic building block of any app. Wraps NSView on macOS. - -use alchemy_styles::{Appearance, Color, Layout}; - -use alchemy_lifecycle::traits::PlatformSpecificNodeType; - -/// A wrapper for `NSView`. This holds retained pointers for the Objective-C -/// runtime - namely, the view itself, and associated things such as background -/// colors and so forth. -#[derive(Debug)] -pub struct View {} - -impl View { - /// Allocates a new `NSView` on the Objective-C side, ensuring that things like coordinate - /// flipping occur (macOS still uses (0,0) as lower-left by default), and opting in to layer - /// backed views for smoother scrolling. - pub fn new() -> View { - View { - } - } - - /// Returns a pointer to the underlying Objective-C view. The pointer is not mutable; however, - /// you can send messages to it (unsafely). - pub fn borrow_native_backing_node(&self) -> PlatformSpecificNodeType { - () - } - - /// Appends a child NSView (or subclassed type) to this view. - pub fn append_child(&mut self, child: PlatformSpecificNodeType) { - } - - /// Given a `&Style`, will set the frame, background color, borders and so forth. It then - /// calls `setNeedsDisplay:YES` on the Objective-C side, so that Cocoa will re-render this - /// view. - pub fn apply_styles(&mut self, appearance: &Appearance, layout: &Layout) { - } -} diff --git a/gtk/src/window.rs b/gtk/src/window.rs deleted file mode 100644 index f04b174..0000000 --- a/gtk/src/window.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! Implements a `gtk::ApplicationWindow` wrapper for GTK-based systems. -//! This also handles looping back lifecycle events, such as window -//! resizing or close events. - -use std::cell::RefCell; - -use gtk::{ - ContainerExt, - GtkWindowExt, WidgetExt, - Window as GtkWindow, WindowType -}; - -use alchemy_lifecycle::traits::WindowDelegate; -use alchemy_styles::Appearance; - -/// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime -/// where our `NSWindow` and associated delegate live. -pub struct Window { - pub inner: GtkWindow -} - -impl Window { - /// Creates a new `NSWindow` instance, configures it appropriately (e.g, titlebar appearance), - /// injects an `NSObject` delegate wrapper, and retains the necessary Objective-C runtime - /// pointers. - pub fn new(content_view: (), app_ptr: *const RefCell) -> Window { - Window { - inner: GtkWindow::new(WindowType::Toplevel) - } - } - - pub fn set_title(&mut self, title: &str) { - self.inner.set_title(title); - } - - pub fn set_dimensions(&mut self, x: f64, y: f64, width: f64, height: f64) { - self.inner.set_position(gtk::WindowPosition::Center); - self.inner.set_default_size(width as i32, height as i32); - } - - /// Normally used for setting platform-specific styles; on macOS we choose not to do this and - /// just have the content view handle the background color, as calling window - /// setBackgroundColor causes some notable lag on resizing. - pub fn apply_styles(&mut self, _appearance: &Appearance) { } - - /// On macOS, calling `show()` is equivalent to calling `makeKeyAndOrderFront`. This is the - /// most common use case, hence why this method was chosen - if you want or need something - /// else, feel free to open an issue to discuss. - /// - /// You should never be calling this yourself, mind you - Alchemy core handles this for you. - pub fn show(&self) { - let button = gtk::Button::new_with_label("Click me!"); - self.inner.add(&button); - self.inner.show_all(); - } - - /// On macOS, calling `close()` is equivalent to calling... well, `close`. It closes the - /// window. - /// - /// I dunno what else to say here, lol. - /// - /// You should never be calling this yourself, mind you - Alchemy core handles this for you. - pub fn close(&self) { - } -} - -impl Drop for Window { - /// When a Window is dropped on the Rust side, we want to ensure that we break the delegate - /// link on the Objective-C side. While this shouldn't actually be an issue, I'd rather be - /// safer than sorry. - fn drop(&mut self) { - } -} diff --git a/lifecycle/Cargo.toml b/lifecycle/Cargo.toml index 05c29a2..a4694db 100644 --- a/lifecycle/Cargo.toml +++ b/lifecycle/Cargo.toml @@ -10,13 +10,10 @@ categories = ["gui", "rendering::engine", "multimedia"] keywords = ["gui", "css", "styles", "layout", "ui"] [features] -default = [] cocoa = ["objc", "objc_id"] -gtkrs = ["gtk"] [dependencies] alchemy-styles = { version = "0.1", path = "../styles" } -gtk = { version = "0.6.0", features = ["v3_16"], optional = true } objc = { version = "0.2.6", optional = true } objc_id = { version = "0.1.1", optional = true } serde_json = "1" diff --git a/lifecycle/src/reconciler/mod.rs b/lifecycle/src/reconciler/mod.rs index d1390d6..093b9dc 100644 --- a/lifecycle/src/reconciler/mod.rs +++ b/lifecycle/src/reconciler/mod.rs @@ -114,7 +114,7 @@ impl RenderEngine { width: Dimension::Points(dimensions.0 as f32), height: Dimension::Points(dimensions.1 as f32) }; - layout_store.set_style(layout, style)?; + layout_store.set_style(layout, style); layout }; diff --git a/lifecycle/src/traits.rs b/lifecycle/src/traits.rs index 3d7d135..1a1d6d6 100644 --- a/lifecycle/src/traits.rs +++ b/lifecycle/src/traits.rs @@ -15,9 +15,8 @@ use crate::rsx::RSX; pub type PlatformSpecificNodeType = objc_id::ShareId; /// A per-platform wrapped Pointer type, used for attaching views/widgets. -#[cfg(feature = "gtkrs")] +#[cfg(not(feature = "cocoa"))] pub type PlatformSpecificNodeType = (); -//pub type PlatformSpecificNodeType = gtk::WidgetExt; /*fn update Box + Send + Sync + 'static>(component: &Component, updater: F) { let component_ptr = component as *const C as usize; @@ -27,7 +26,7 @@ pub type PlatformSpecificNodeType = (); /// Each platform tends to have their own startup routine, their own runloop, and so on. /// Alchemy recognizes this and provides an `AppDelegate` that receives events at a system /// level and allows the user to operate within the established framework per-system. -pub trait AppDelegate { +pub trait AppDelegate: Send + Sync { /// Fired when an Application is about to finish launching. fn will_finish_launching(&mut self) {} @@ -62,7 +61,7 @@ pub trait AppDelegate { /// Each platform has their own `Window` API, which Alchemy attempts to pair down to one consistent /// API. This also acts as the bootstrapping point for a `render` tree. -pub trait WindowDelegate { +pub trait WindowDelegate: Send + Sync { /// Fired when this Window will close. You can use this to clean up or destroy resources, /// timers, and other things. fn will_close(&mut self) { } @@ -170,7 +169,7 @@ pub trait Component: Props + Send + Sync { /// lifecycle methods instead. Keeping `render()` pure makes components easier to think about. /// /// This method is not called if should_component_update() returns `false`. - fn render(&self, _children: Vec) -> Result { Ok(RSX::None) } + fn render(&self, children: Vec) -> Result { Ok(RSX::None) } /// This lifecycle is invoked after an error has been thrown by a descendant component. It receives /// the error that was thrown as a parameter and should return a value to update state.