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).
+
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.