diff --git a/README.md b/README.md
index 0100bd6..dbe75fd 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,3 @@
-# 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 a376ab6..45a006c 100644
--- a/alchemy/Cargo.toml
+++ b/alchemy/Cargo.toml
@@ -13,10 +13,13 @@ 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 f1230d2..be47cf7 100644
--- a/alchemy/src/app.rs
+++ b/alchemy/src/app.rs
@@ -1,79 +1,49 @@
//! This module implements the Application structure and associated
-//! 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.
+//! lifecycle methods.
//!
//! This ensures that you can respond to application lifecycles, and so
//! routing things around works correctly.
-use std::sync::{Arc, Mutex};
+use std::cell::RefCell;
-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};
-/// 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 {}
+#[cfg(feature = "gtkrs")]
+pub use alchemy_gtkrs::app::{App as PlatformAppBridge};
/// 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. The `ThemeEngine` and `WindowManager` are
-/// also stored here for easy access.
+/// delegate to forward events to.
pub struct App {
- pub(crate) bridge: Mutex>,
- pub delegate: Mutex>,
- pub windows: WindowManager
+ pub bridge: Option>,
+ pub delegate: RefCell>
}
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(crate) fn new() -> Arc {
- let app = Arc::new(App {
- bridge: Mutex::new(None),
- delegate: Mutex::new(Box::new(DefaultAppDelegate {})),
- windows: WindowManager::new()
+ pub fn new(state: S) -> Box {
+ let mut app = Box::new(App {
+ bridge: None,
+ delegate: RefCell::new(Box::new(state))
});
let app_ptr: *const App = &*app;
- app.configure_bridge(app_ptr);
+ app.bridge = Some(RefCell::new(PlatformAppBridge::new(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, 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();
+ pub fn run(&self) {
+ if let Some(bridge) = &self.bridge {
+ bridge.borrow_mut().run();
}
}
}
@@ -85,41 +55,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.lock().unwrap();
+ let mut delegate = self.delegate.borrow_mut();
delegate.will_finish_launching();
}
/// Called when the application did finish launching.
fn did_finish_launching(&mut self) {
- let mut delegate = self.delegate.lock().unwrap();
+ let mut delegate = self.delegate.borrow_mut();
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.lock().unwrap();
+ let mut delegate = self.delegate.borrow_mut();
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.lock().unwrap();
+ let mut delegate = self.delegate.borrow_mut();
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.lock().unwrap();
+ let mut delegate = self.delegate.borrow_mut();
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.lock().unwrap();
+ let mut delegate = self.delegate.borrow_mut();
delegate.did_resign_active();
}
@@ -127,20 +97,13 @@ 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.lock().unwrap();
+ let delegate = self.delegate.borrow_mut();
delegate.should_terminate()
}
/// Called when the application is about to terminate.
fn will_terminate(&mut self) {
- let mut delegate = self.delegate.lock().unwrap();
+ let mut delegate = self.delegate.borrow_mut();
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 105a537..cba0d8c 100644
--- a/alchemy/src/components/text.rs
+++ b/alchemy/src/components/text.rs
@@ -15,6 +15,9 @@ 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 a8ccdaf..6f0bb9c 100644
--- a/alchemy/src/components/view.rs
+++ b/alchemy/src/components/view.rs
@@ -17,6 +17,9 @@ 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 3bf0bfd..efcbcef 100644
--- a/alchemy/src/lib.rs
+++ b/alchemy/src/lib.rs
@@ -5,7 +5,6 @@
//! 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;
@@ -29,21 +28,10 @@ pub use alchemy_macros::Props;
pub use alchemy_styles::{Color, styles as style_attributes, SpacedSet, StyleSheet, StylesList};
mod app;
-use app::App;
+pub 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/window.rs b/alchemy/src/window.rs
similarity index 59%
rename from alchemy/src/window/window.rs
rename to alchemy/src/window.rs
index 9c4a84b..014d8c9 100644
--- a/alchemy/src/window/window.rs
+++ b/alchemy/src/window.rs
@@ -1,7 +1,8 @@
//! Implements the Window API. It attempts to provide a nice, common interface across
//! per-platform Window APIs.
-use std::sync::{Arc, Mutex};
+use std::rc::Rc;
+use std::cell::RefCell;
use alchemy_lifecycle::{ComponentKey, RENDER_ENGINE};
use alchemy_lifecycle::rsx::RSX;
@@ -9,23 +10,21 @@ 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};
-/// 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.
+#[cfg(feature = "gtkrs")]
+use alchemy_gtkrs::window::{Window as PlatformWindowBridge};
+
pub struct AppWindow {
- pub id: usize,
- pub style_keys: StylesList,
+ pub styles: StylesList,
pub title: String,
pub dimensions: (f64, f64, f64, f64),
- pub bridge: PlatformWindowBridge,
+ pub bridge: Option,
pub delegate: Box,
- pub render_key: ComponentKey
+ render_key: Option
}
impl AppWindow {
@@ -39,9 +38,11 @@ impl AppWindow {
pub fn render(&mut self) {
let mut style = Style::default();
let mut appearance = Appearance::default();
- THEME_ENGINE.configure_styles_for_keys(&self.style_keys, &mut style, &mut appearance);
+ THEME_ENGINE.configure_styles_for_keys(&self.styles, &mut style, &mut appearance);
- self.bridge.apply_styles(&appearance);
+ if let Some(bridge) = &mut self.bridge {
+ bridge.apply_styles(&appearance);
+ }
let children = match self.delegate.render() {
Ok(opt) => opt,
@@ -51,90 +52,102 @@ impl AppWindow {
}
};
- 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); }
+ 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); }
+ }
}
}
pub fn set_title(&mut self, title: &str) {
self.title = title.into();
- self.bridge.set_title(title);
+ if let Some(bridge) = &mut self.bridge {
+ bridge.set_title(title);
+ }
}
pub fn set_dimensions(&mut self, x: f64, y: f64, width: f64, height: f64) {
self.dimensions = (x, y, width, height);
- self.bridge.set_dimensions(x, y, width, height);
+ if let Some(bridge) = &mut self.bridge {
+ 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();
- self.bridge.show();
+ if let Some(bridge) = &mut self.bridge {
+ bridge.show();
+ }
}
/// Calls through to the native platform window close method.
pub fn close(&mut self) {
- self.bridge.close();
+ if let Some(bridge) = &mut self.bridge {
+ bridge.close();
+ }
}
}
-/// 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 WindowDelegate for AppWindow {}
+
+/// Window represents... well, a Window. When you create one, you get the Window back.
+pub struct Window(pub Rc>);
impl Window {
/// Creates a new window.
- 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(),
+ pub fn new(delegate: S) -> Window {
+ let app_window = Rc::new(RefCell::new(AppWindow {
+ styles: "".into(),
title: "".into(),
dimensions: (0., 0., 0., 0.),
- bridge: bridge,
+ bridge: None,
delegate: Box::new(delegate),
- render_key: key
- })))
+ 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)
}
/// 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 mut window = self.0.lock().unwrap();
+ let window = self.0.borrow_mut();
window.render();
}
pub fn set_title(&self, title: &str) {
- let mut window = self.0.lock().unwrap();
+ let mut window = self.0.borrow_mut();
window.set_title(title);
}
pub fn set_dimensions(&mut self, x: f64, y: f64, width: f64, height: f64) {
- let mut window = self.0.lock().unwrap();
+ let mut window = self.0.borrow_mut();
window.set_dimensions(x, y, width, height);
}
/// Registers this window with the window manager, renders it, and shows it.
pub fn show(&self) {
- SHARED_APP.windows.add(self.0.clone());
- let mut window = self.0.lock().unwrap();
+ let mut window = self.0.borrow_mut();
window.show();
}
@@ -147,9 +160,7 @@ 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 window_id = self.0.lock().unwrap().id;
- SHARED_APP.windows.will_close(window_id);
- let mut window = self.0.lock().unwrap();
+ let mut window = self.0.borrow_mut();
window.close();
}
}
diff --git a/alchemy/src/window/manager.rs b/alchemy/src/window/manager.rs
deleted file mode 100644
index 630c6e0..0000000
--- a/alchemy/src/window/manager.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-//! 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
deleted file mode 100644
index 5cc5848..0000000
--- a/alchemy/src/window/mod.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-//! 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/cocoa/src/window.rs b/cocoa/src/window.rs
index 64b4dd9..798b2a6 100644
--- a/cocoa/src/window.rs
+++ b/cocoa/src/window.rs
@@ -2,6 +2,8 @@
//! 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};
@@ -13,11 +15,10 @@ use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{msg_send, sel, sel_impl};
-use alchemy_lifecycle::traits::{AppDelegate, Component};
+use alchemy_lifecycle::traits::WindowDelegate;
use alchemy_styles::Appearance;
-static APP_PTR: &str = "alchemyAppPtr";
-static WINDOW_MANAGER_ID: &str = "alchemyWindowManagerID";
+static WINDOW_PTR: &str = "alchemyWindowPtr";
/// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSWindow` and associated delegate live.
@@ -30,7 +31,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(window_id: usize, content_view: ShareId, app_ptr: *const T) -> Window {
+ pub fn new(content_view: ShareId, window_ptr: *const RefCell) -> Window {
let dimensions = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.));
let style = NSWindowStyleMask::NSResizableWindowMask |
@@ -45,17 +46,13 @@ 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];
- //if let Some(view_ptr) = content_view.borrow_native_backing_node() {
- msg_send![window, setContentView:content_view];
- //}
+ msg_send![window, setTitlebarAppearsTransparent:YES];
+ msg_send![window, setContentView:content_view];
ShareId::from_ptr(window)
};
@@ -63,8 +60,7 @@ impl Window {
let delegate = unsafe {
let delegate_class = register_window_class::();
let delegate: id = msg_send![delegate_class, new];
- (&mut *delegate).set_ivar(APP_PTR, app_ptr as usize);
- (&mut *delegate).set_ivar(WINDOW_MANAGER_ID, window_id);
+ (&mut *delegate).set_ivar(WINDOW_PTR, window_ptr as usize);
msg_send![inner, setDelegate:delegate];
ShareId::from_ptr(delegate)
};
@@ -136,27 +132,25 @@ 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 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);
+ let window_ptr: usize = *this.get_ivar(WINDOW_PTR);
+ let window = window_ptr as *mut T;
+ (*window).will_close();
};
}
/// 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::(APP_PTR);
- decl.add_ivar::(WINDOW_MANAGER_ID);
+ decl.add_ivar::(WINDOW_PTR);
decl.add_method(sel!(windowWillClose:), will_close:: as extern fn(&Object, _, _));
diff --git a/examples/layout/Cargo.toml b/examples/layout/Cargo.toml
index c3cc60e..419c159 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 = ["cocoa"] }
+alchemy = { path = "../../alchemy", version = "0.2.0", features = ["gtkrs"] }
diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs
index 2ea858f..0861b5c 100644
--- a/examples/layout/src/main.rs
+++ b/examples/layout/src/main.rs
@@ -8,40 +8,22 @@
/// @created March 26th, 2019
use alchemy::{
- AppDelegate, Component, ComponentKey, Fragment, Error, Props, rsx, RSX, styles, text,
- Text, View, Window, WindowDelegate
+ App, AppDelegate, Error, rsx,
+ RSX, text, Text, View, Window, WindowDelegate
};
pub struct AppState {
- window: Window
+ window: Option
}
impl AppDelegate for AppState {
fn did_finish_launching(&mut self) {
- 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}
-
- })
+ 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");
}
}
@@ -54,6 +36,7 @@ impl WindowDelegate for WindowState {
fn render(&self) -> Result {
let messages = vec!["LOL"]; //, "wut", "BERT"];
+
Ok(rsx! {
"Hello there, my name is Bert"
@@ -61,64 +44,13 @@ impl WindowDelegate for WindowState {
{messages.iter().map(|message| rsx! {
{text!("{}", message)}
})}
-
-
-
-
-
})
}
}
fn main() {
- 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 {
-
- })
- });
+ App::new(AppState {
+ window: None
+ }).run()
}
diff --git a/gtk/Cargo.toml b/gtk/Cargo.toml
new file mode 100644
index 0000000..cff6464
--- /dev/null
+++ b/gtk/Cargo.toml
@@ -0,0 +1,19 @@
+[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
new file mode 100644
index 0000000..8f09e1f
--- /dev/null
+++ b/gtk/src/app.rs
@@ -0,0 +1,44 @@
+//! 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
new file mode 100644
index 0000000..645916b
--- /dev/null
+++ b/gtk/src/lib.rs
@@ -0,0 +1,22 @@
+//! 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
new file mode 100644
index 0000000..c6e11b0
--- /dev/null
+++ b/gtk/src/text.rs
@@ -0,0 +1,47 @@
+//! 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
new file mode 100644
index 0000000..915b054
--- /dev/null
+++ b/gtk/src/view.rs
@@ -0,0 +1,38 @@
+//! 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
new file mode 100644
index 0000000..f04b174
--- /dev/null
+++ b/gtk/src/window.rs
@@ -0,0 +1,73 @@
+//! 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 a4694db..05c29a2 100644
--- a/lifecycle/Cargo.toml
+++ b/lifecycle/Cargo.toml
@@ -10,10 +10,13 @@ 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 093b9dc..d1390d6 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 1a1d6d6..3d7d135 100644
--- a/lifecycle/src/traits.rs
+++ b/lifecycle/src/traits.rs
@@ -15,8 +15,9 @@ use crate::rsx::RSX;
pub type PlatformSpecificNodeType = objc_id::ShareId;
/// A per-platform wrapped Pointer type, used for attaching views/widgets.
-#[cfg(not(feature = "cocoa"))]
+#[cfg(feature = "gtkrs")]
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;
@@ -26,7 +27,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: Send + Sync {
+pub trait AppDelegate {
/// Fired when an Application is about to finish launching.
fn will_finish_launching(&mut self) {}
@@ -61,7 +62,7 @@ pub trait AppDelegate: Send + Sync {
/// 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: Send + Sync {
+pub trait WindowDelegate {
/// 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) { }
@@ -169,7 +170,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.