Ongoing work on supporting GTK, had to rework some concepts due to the way gtk-rs works.
This commit is contained in:
parent
f15cf258af
commit
0d8a14ce67
20 changed files with 378 additions and 301 deletions
|
|
@ -13,10 +13,13 @@ keywords = ["gui", "css", "styles", "layout", "react"]
|
||||||
maintenance = { status = "actively-developed" }
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = []
|
||||||
cocoa = ["alchemy-cocoa", "alchemy-lifecycle/cocoa"]
|
cocoa = ["alchemy-cocoa", "alchemy-lifecycle/cocoa"]
|
||||||
|
gtkrs = ["alchemy-gtkrs", "alchemy-lifecycle/gtkrs"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
alchemy-cocoa = { version = "0.1", path = "../cocoa", optional = true }
|
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-lifecycle = { version = "0.1", path = "../lifecycle" }
|
||||||
alchemy-macros = { version = "0.1", path = "../macros" }
|
alchemy-macros = { version = "0.1", path = "../macros" }
|
||||||
alchemy-styles = { version = "0.1", path = "../styles", features = ["parser"] }
|
alchemy-styles = { version = "0.1", path = "../styles", features = ["parser"] }
|
||||||
|
|
|
||||||
|
|
@ -1,79 +1,49 @@
|
||||||
//! This module implements the Application structure and associated
|
//! This module implements the Application structure and associated
|
||||||
//! lifecycle methods. You typically never create this struct yourself;
|
//! lifecycle methods.
|
||||||
//! 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
|
//! This ensures that you can respond to application lifecycles, and so
|
||||||
//! routing things around works correctly.
|
//! 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 alchemy_lifecycle::traits::AppDelegate;
|
||||||
|
|
||||||
use crate::window::WindowManager;
|
|
||||||
|
|
||||||
#[cfg(feature = "cocoa")]
|
#[cfg(feature = "cocoa")]
|
||||||
pub use alchemy_cocoa::app::{App as PlatformAppBridge};
|
pub use alchemy_cocoa::app::{App as PlatformAppBridge};
|
||||||
|
|
||||||
/// A default delegate that is mostly used for creating the initial struct,
|
#[cfg(feature = "gtkrs")]
|
||||||
/// without requiring the actual `AppDelegate` from the user. Will ideally
|
pub use alchemy_gtkrs::app::{App as PlatformAppBridge};
|
||||||
/// never see the light of day.
|
|
||||||
struct DefaultAppDelegate;
|
|
||||||
impl AppDelegate for DefaultAppDelegate {}
|
|
||||||
|
|
||||||
/// The Application structure itself. It holds a Mutex'd platform bridge, to
|
/// The Application structure itself. It holds a Mutex'd platform bridge, to
|
||||||
/// handle communicating with the platform-specific app instance, along with a
|
/// handle communicating with the platform-specific app instance, along with a
|
||||||
/// delegate to forward events to. The `ThemeEngine` and `WindowManager` are
|
/// delegate to forward events to.
|
||||||
/// also stored here for easy access.
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub(crate) bridge: Mutex<Option<PlatformAppBridge>>,
|
pub bridge: Option<RefCell<PlatformAppBridge>>,
|
||||||
pub delegate: Mutex<Box<AppDelegate>>,
|
pub delegate: RefCell<Box<AppDelegate>>
|
||||||
pub windows: WindowManager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
/// Creates a new app, allocated on the heap. Provides a pointer to
|
/// Creates a new app, allocated on the heap. Provides a pointer to
|
||||||
/// said allocated instance so that the platform-specific app instances
|
/// said allocated instance so that the platform-specific app instances
|
||||||
/// can loop events back around.
|
/// can loop events back around.
|
||||||
pub(crate) fn new() -> Arc<App> {
|
pub fn new<S: AppDelegate + 'static>(state: S) -> Box<App> {
|
||||||
let app = Arc::new(App {
|
let mut app = Box::new(App {
|
||||||
bridge: Mutex::new(None),
|
bridge: None,
|
||||||
delegate: Mutex::new(Box::new(DefaultAppDelegate {})),
|
delegate: RefCell::new(Box::new(state))
|
||||||
windows: WindowManager::new()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let app_ptr: *const App = &*app;
|
let app_ptr: *const App = &*app;
|
||||||
app.configure_bridge(app_ptr);
|
app.bridge = Some(RefCell::new(PlatformAppBridge::new(app_ptr)));
|
||||||
|
|
||||||
app
|
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
|
/// 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
|
/// 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()`.
|
/// will want to begin your app (for real) in `AppDelegate::did_finish_launching()`.
|
||||||
pub fn run<S: 'static + AppDelegate>(&self, state: S) {
|
pub fn run(&self) {
|
||||||
{
|
if let Some(bridge) = &self.bridge {
|
||||||
let mut delegate = self.delegate.lock().unwrap();
|
bridge.borrow_mut().run();
|
||||||
*delegate = Box::new(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
let lock = self.bridge.lock().unwrap();
|
|
||||||
if let Some(bridge) = &*lock {
|
|
||||||
bridge.run();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,41 +55,41 @@ impl App {
|
||||||
impl AppDelegate for App {
|
impl AppDelegate for App {
|
||||||
/// Called when the application will finish launching.
|
/// Called when the application will finish launching.
|
||||||
fn will_finish_launching(&mut self) {
|
fn will_finish_launching(&mut self) {
|
||||||
let mut delegate = self.delegate.lock().unwrap();
|
let mut delegate = self.delegate.borrow_mut();
|
||||||
delegate.will_finish_launching();
|
delegate.will_finish_launching();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when the application did finish launching.
|
/// Called when the application did finish launching.
|
||||||
fn did_finish_launching(&mut self) {
|
fn did_finish_launching(&mut self) {
|
||||||
let mut delegate = self.delegate.lock().unwrap();
|
let mut delegate = self.delegate.borrow_mut();
|
||||||
delegate.did_finish_launching();
|
delegate.did_finish_launching();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when the application will become active. We can use this, for instance,
|
/// Called when the application will become active. We can use this, for instance,
|
||||||
/// to resume rendering cycles and so on.
|
/// to resume rendering cycles and so on.
|
||||||
fn will_become_active(&mut self) {
|
fn will_become_active(&mut self) {
|
||||||
let mut delegate = self.delegate.lock().unwrap();
|
let mut delegate = self.delegate.borrow_mut();
|
||||||
delegate.will_become_active();
|
delegate.will_become_active();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when the application did become active. We can use this, for instance,
|
/// Called when the application did become active. We can use this, for instance,
|
||||||
/// to resume rendering cycles and so on.
|
/// to resume rendering cycles and so on.
|
||||||
fn did_become_active(&mut self) {
|
fn did_become_active(&mut self) {
|
||||||
let mut delegate = self.delegate.lock().unwrap();
|
let mut delegate = self.delegate.borrow_mut();
|
||||||
delegate.did_become_active();
|
delegate.did_become_active();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when the application will resigned active. We can use this, for instance,
|
/// Called when the application will resigned active. We can use this, for instance,
|
||||||
/// to pause rendering cycles and so on.
|
/// to pause rendering cycles and so on.
|
||||||
fn will_resign_active(&mut self) {
|
fn will_resign_active(&mut self) {
|
||||||
let mut delegate = self.delegate.lock().unwrap();
|
let mut delegate = self.delegate.borrow_mut();
|
||||||
delegate.will_resign_active();
|
delegate.will_resign_active();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when the application has resigned active. We can use this, for instance,
|
/// Called when the application has resigned active. We can use this, for instance,
|
||||||
/// to pause rendering cycles and so on.
|
/// to pause rendering cycles and so on.
|
||||||
fn did_resign_active(&mut self) {
|
fn did_resign_active(&mut self) {
|
||||||
let mut delegate = self.delegate.lock().unwrap();
|
let mut delegate = self.delegate.borrow_mut();
|
||||||
delegate.did_resign_active();
|
delegate.did_resign_active();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,20 +97,13 @@ impl AppDelegate for App {
|
||||||
/// to avoid termination if Alchemy needs more time for something,
|
/// to avoid termination if Alchemy needs more time for something,
|
||||||
/// for whatever reason.
|
/// for whatever reason.
|
||||||
fn should_terminate(&self) -> bool {
|
fn should_terminate(&self) -> bool {
|
||||||
let delegate = self.delegate.lock().unwrap();
|
let delegate = self.delegate.borrow_mut();
|
||||||
delegate.should_terminate()
|
delegate.should_terminate()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when the application is about to terminate.
|
/// Called when the application is about to terminate.
|
||||||
fn will_terminate(&mut self) {
|
fn will_terminate(&mut self) {
|
||||||
let mut delegate = self.delegate.lock().unwrap();
|
let mut delegate = self.delegate.borrow_mut();
|
||||||
delegate.will_terminate();
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ use alchemy_lifecycle::traits::{Component, Props, PlatformSpecificNodeType};
|
||||||
#[cfg(feature = "cocoa")]
|
#[cfg(feature = "cocoa")]
|
||||||
use alchemy_cocoa::text::{Text as PlatformTextBridge};
|
use alchemy_cocoa::text::{Text as PlatformTextBridge};
|
||||||
|
|
||||||
|
#[cfg(feature = "gtkrs")]
|
||||||
|
use alchemy_gtkrs::text::{Text as PlatformTextBridge};
|
||||||
|
|
||||||
pub struct TextProps;
|
pub struct TextProps;
|
||||||
|
|
||||||
/// Text rendering is a complicated mess, and being able to defer to the
|
/// Text rendering is a complicated mess, and being able to defer to the
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ use crate::components::Fragment;
|
||||||
#[cfg(feature = "cocoa")]
|
#[cfg(feature = "cocoa")]
|
||||||
use alchemy_cocoa::view::{View as PlatformViewBridge};
|
use alchemy_cocoa::view::{View as PlatformViewBridge};
|
||||||
|
|
||||||
|
#[cfg(feature = "gtkrs")]
|
||||||
|
use alchemy_gtkrs::view::{View as PlatformViewBridge};
|
||||||
|
|
||||||
pub struct ViewProps;
|
pub struct ViewProps;
|
||||||
|
|
||||||
/// Views are the most basic piece of the API. If you want to display something, you'll
|
/// Views are the most basic piece of the API. If you want to display something, you'll
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
//! CSS support (no cascading) provides a familiar syntax for developers who tend to work on
|
//! 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.
|
//! 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;
|
pub use lazy_static::lazy_static;
|
||||||
use proc_macro_hack::proc_macro_hack;
|
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};
|
pub use alchemy_styles::{Color, styles as style_attributes, SpacedSet, StyleSheet, StylesList};
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
use app::App;
|
pub use app::App;
|
||||||
|
|
||||||
pub mod components;
|
pub mod components;
|
||||||
pub use components::{Fragment, Text, View};
|
pub use components::{Fragment, Text, View};
|
||||||
|
|
||||||
pub mod window;
|
pub mod window;
|
||||||
pub use window::Window;
|
pub use window::Window;
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub(crate) static ref SHARED_APP: Arc<App> = 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<App> {
|
|
||||||
SHARED_APP.clone()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
//! Implements the Window API. It attempts to provide a nice, common interface across
|
//! Implements the Window API. It attempts to provide a nice, common interface across
|
||||||
//! per-platform Window APIs.
|
//! 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::{ComponentKey, RENDER_ENGINE};
|
||||||
use alchemy_lifecycle::rsx::RSX;
|
use alchemy_lifecycle::rsx::RSX;
|
||||||
|
|
@ -9,23 +10,21 @@ use alchemy_lifecycle::traits::{Component, WindowDelegate};
|
||||||
|
|
||||||
use alchemy_styles::{Appearance, Style, StylesList, THEME_ENGINE};
|
use alchemy_styles::{Appearance, Style, StylesList, THEME_ENGINE};
|
||||||
|
|
||||||
use crate::{App, SHARED_APP};
|
|
||||||
use crate::components::View;
|
use crate::components::View;
|
||||||
|
|
||||||
#[cfg(feature = "cocoa")]
|
#[cfg(feature = "cocoa")]
|
||||||
use alchemy_cocoa::window::{Window as PlatformWindowBridge};
|
use alchemy_cocoa::window::{Window as PlatformWindowBridge};
|
||||||
|
|
||||||
/// AppWindow contains the inner details of a Window. It's guarded by a Mutex on `Window`,
|
#[cfg(feature = "gtkrs")]
|
||||||
/// and you shouldn't create this yourself, but it's documented here so you can understand what
|
use alchemy_gtkrs::window::{Window as PlatformWindowBridge};
|
||||||
/// it holds.
|
|
||||||
pub struct AppWindow {
|
pub struct AppWindow {
|
||||||
pub id: usize,
|
pub styles: StylesList,
|
||||||
pub style_keys: StylesList,
|
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub dimensions: (f64, f64, f64, f64),
|
pub dimensions: (f64, f64, f64, f64),
|
||||||
pub bridge: PlatformWindowBridge,
|
pub bridge: Option<PlatformWindowBridge>,
|
||||||
pub delegate: Box<WindowDelegate>,
|
pub delegate: Box<WindowDelegate>,
|
||||||
pub render_key: ComponentKey
|
render_key: Option<ComponentKey>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppWindow {
|
impl AppWindow {
|
||||||
|
|
@ -39,9 +38,11 @@ impl AppWindow {
|
||||||
pub fn render(&mut self) {
|
pub fn render(&mut self) {
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
let mut appearance = Appearance::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() {
|
let children = match self.delegate.render() {
|
||||||
Ok(opt) => opt,
|
Ok(opt) => opt,
|
||||||
|
|
@ -51,7 +52,8 @@ impl AppWindow {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match RENDER_ENGINE.diff_and_render_root(self.render_key, (
|
if let Some(render_key) = self.render_key {
|
||||||
|
match RENDER_ENGINE.diff_and_render_root(render_key, (
|
||||||
self.dimensions.2,
|
self.dimensions.2,
|
||||||
self.dimensions.3
|
self.dimensions.3
|
||||||
), children) {
|
), children) {
|
||||||
|
|
@ -59,82 +61,93 @@ impl AppWindow {
|
||||||
Err(e) => { eprintln!("Error rendering window! {}", e); }
|
Err(e) => { eprintln!("Error rendering window! {}", e); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_title(&mut self, title: &str) {
|
pub fn set_title(&mut self, title: &str) {
|
||||||
self.title = title.into();
|
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) {
|
pub fn set_dimensions(&mut self, x: f64, y: f64, width: f64, height: f64) {
|
||||||
self.dimensions = (x, y, width, height);
|
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.
|
/// Renders and calls through to the native platform window show method.
|
||||||
pub fn show(&mut self) {
|
pub fn show(&mut self) {
|
||||||
self.render();
|
self.render();
|
||||||
self.bridge.show();
|
if let Some(bridge) = &mut self.bridge {
|
||||||
|
bridge.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls through to the native platform window close method.
|
/// Calls through to the native platform window close method.
|
||||||
pub fn close(&mut self) {
|
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
|
impl WindowDelegate for AppWindow {}
|
||||||
/// show one, a clone of the pointer is added to the window manager, and removed on close.
|
|
||||||
pub struct Window(pub(crate) Arc<Mutex<AppWindow>>);
|
/// Window represents... well, a Window. When you create one, you get the Window back.
|
||||||
|
pub struct Window(pub Rc<RefCell<AppWindow>>);
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
/// Creates a new window.
|
/// Creates a new window.
|
||||||
pub fn new<S: 'static + WindowDelegate>(delegate: S) -> Window {
|
pub fn new<S: 'static + WindowDelegate>(delegate: S) -> Window {
|
||||||
let window_id = SHARED_APP.windows.allocate_new_window_id();
|
let app_window = Rc::new(RefCell::new(AppWindow {
|
||||||
let view = View::default();
|
styles: "".into(),
|
||||||
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(),
|
title: "".into(),
|
||||||
dimensions: (0., 0., 0., 0.),
|
dimensions: (0., 0., 0., 0.),
|
||||||
bridge: bridge,
|
bridge: None,
|
||||||
delegate: Box::new(delegate),
|
delegate: Box::new(delegate),
|
||||||
render_key: key
|
render_key: None
|
||||||
})))
|
}));
|
||||||
|
|
||||||
|
let app_ptr: *const RefCell<AppWindow> = &*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()`
|
/// 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
|
/// on your `WindowDelegate`. Note that calling `.show()` implicitly calls this for you, so you
|
||||||
/// rarely need to call this yourself.
|
/// rarely need to call this yourself.
|
||||||
pub fn render(&self) {
|
pub fn render(&self) {
|
||||||
let mut window = self.0.lock().unwrap();
|
let window = self.0.borrow_mut();
|
||||||
window.render();
|
window.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_title(&self, title: &str) {
|
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);
|
window.set_title(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_dimensions(&mut self, x: f64, y: f64, width: f64, height: f64) {
|
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);
|
window.set_dimensions(x, y, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers this window with the window manager, renders it, and shows it.
|
/// Registers this window with the window manager, renders it, and shows it.
|
||||||
pub fn show(&self) {
|
pub fn show(&self) {
|
||||||
SHARED_APP.windows.add(self.0.clone());
|
let mut window = self.0.borrow_mut();
|
||||||
let mut window = self.0.lock().unwrap();
|
|
||||||
window.show();
|
window.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,9 +160,7 @@ impl Window {
|
||||||
/// Closes the window, unregistering it from the window manager in the process and ensuring the
|
/// Closes the window, unregistering it from the window manager in the process and ensuring the
|
||||||
/// necessary delegate method(s) are fired.
|
/// necessary delegate method(s) are fired.
|
||||||
pub fn close(&self) {
|
pub fn close(&self) {
|
||||||
let window_id = self.0.lock().unwrap().id;
|
let mut window = self.0.borrow_mut();
|
||||||
SHARED_APP.windows.will_close(window_id);
|
|
||||||
let mut window = self.0.lock().unwrap();
|
|
||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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<Vec<Arc<Mutex<AppWindow>>>>);
|
|
||||||
|
|
||||||
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<Mutex<AppWindow>>) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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};
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
//! Cocoa and associated widgets. This also handles looping back
|
//! Cocoa and associated widgets. This also handles looping back
|
||||||
//! lifecycle events, such as window resizing or close events.
|
//! lifecycle events, such as window resizing or close events.
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use std::sync::{Once, ONCE_INIT};
|
use std::sync::{Once, ONCE_INIT};
|
||||||
|
|
||||||
use cocoa::base::{id, nil, YES, NO};
|
use cocoa::base::{id, nil, YES, NO};
|
||||||
|
|
@ -13,11 +15,10 @@ use objc::declare::ClassDecl;
|
||||||
use objc::runtime::{Class, Object, Sel};
|
use objc::runtime::{Class, Object, Sel};
|
||||||
use objc::{msg_send, sel, sel_impl};
|
use objc::{msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use alchemy_lifecycle::traits::{AppDelegate, Component};
|
use alchemy_lifecycle::traits::WindowDelegate;
|
||||||
use alchemy_styles::Appearance;
|
use alchemy_styles::Appearance;
|
||||||
|
|
||||||
static APP_PTR: &str = "alchemyAppPtr";
|
static WINDOW_PTR: &str = "alchemyWindowPtr";
|
||||||
static WINDOW_MANAGER_ID: &str = "alchemyWindowManagerID";
|
|
||||||
|
|
||||||
/// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime
|
/// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime
|
||||||
/// where our `NSWindow` and associated delegate live.
|
/// 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),
|
/// Creates a new `NSWindow` instance, configures it appropriately (e.g, titlebar appearance),
|
||||||
/// injects an `NSObject` delegate wrapper, and retains the necessary Objective-C runtime
|
/// injects an `NSObject` delegate wrapper, and retains the necessary Objective-C runtime
|
||||||
/// pointers.
|
/// pointers.
|
||||||
pub fn new<T: AppDelegate>(window_id: usize, content_view: ShareId<Object>, app_ptr: *const T) -> Window {
|
pub fn new<T: WindowDelegate>(content_view: ShareId<Object>, window_ptr: *const RefCell<T>) -> Window {
|
||||||
let dimensions = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.));
|
let dimensions = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.));
|
||||||
|
|
||||||
let style = NSWindowStyleMask::NSResizableWindowMask |
|
let style = NSWindowStyleMask::NSResizableWindowMask |
|
||||||
|
|
@ -45,17 +46,13 @@ impl Window {
|
||||||
NO
|
NO
|
||||||
).autorelease();
|
).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
|
// 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
|
// to disable, like... this. If we don't set this, we'll segfault entirely because the
|
||||||
// Objective-C runtime gets out of sync.
|
// Objective-C runtime gets out of sync.
|
||||||
msg_send![window, setReleasedWhenClosed:NO];
|
msg_send![window, setReleasedWhenClosed:NO];
|
||||||
|
|
||||||
//if let Some(view_ptr) = content_view.borrow_native_backing_node() {
|
msg_send![window, setTitlebarAppearsTransparent:YES];
|
||||||
msg_send![window, setContentView:content_view];
|
msg_send![window, setContentView:content_view];
|
||||||
//}
|
|
||||||
|
|
||||||
ShareId::from_ptr(window)
|
ShareId::from_ptr(window)
|
||||||
};
|
};
|
||||||
|
|
@ -63,8 +60,7 @@ impl Window {
|
||||||
let delegate = unsafe {
|
let delegate = unsafe {
|
||||||
let delegate_class = register_window_class::<T>();
|
let delegate_class = register_window_class::<T>();
|
||||||
let delegate: id = msg_send![delegate_class, new];
|
let delegate: id = msg_send![delegate_class, new];
|
||||||
(&mut *delegate).set_ivar(APP_PTR, app_ptr as usize);
|
(&mut *delegate).set_ivar(WINDOW_PTR, window_ptr as usize);
|
||||||
(&mut *delegate).set_ivar(WINDOW_MANAGER_ID, window_id);
|
|
||||||
msg_send![inner, setDelegate:delegate];
|
msg_send![inner, setDelegate:delegate];
|
||||||
ShareId::from_ptr(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
|
/// Called when a Window receives a `windowWillClose:` event. Loops back to the shared
|
||||||
/// Alchemy app instance, so that our window manager can act appropriately.
|
/// Alchemy app instance, so that our window manager can act appropriately.
|
||||||
extern fn will_close<T: AppDelegate>(this: &Object, _: Sel, _: id) {
|
extern fn will_close<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let app_ptr: usize = *this.get_ivar(APP_PTR);
|
let window_ptr: usize = *this.get_ivar(WINDOW_PTR);
|
||||||
let window_id: usize = *this.get_ivar(WINDOW_MANAGER_ID);
|
let window = window_ptr as *mut T;
|
||||||
let app = app_ptr as *mut T;
|
(*window).will_close();
|
||||||
(*app)._window_will_close(window_id);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Injects an `NSObject` delegate subclass, with some callback and pointer ivars for what we
|
/// Injects an `NSObject` delegate subclass, with some callback and pointer ivars for what we
|
||||||
/// need to do.
|
/// need to do.
|
||||||
fn register_window_class<T: AppDelegate>() -> *const Class {
|
fn register_window_class<T: WindowDelegate>() -> *const Class {
|
||||||
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
|
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
|
||||||
static INIT: Once = ONCE_INIT;
|
static INIT: Once = ONCE_INIT;
|
||||||
|
|
||||||
INIT.call_once(|| unsafe {
|
INIT.call_once(|| unsafe {
|
||||||
let superclass = Class::get("NSObject").unwrap();
|
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::<usize>(APP_PTR);
|
decl.add_ivar::<usize>(WINDOW_PTR);
|
||||||
decl.add_ivar::<usize>(WINDOW_MANAGER_ID);
|
|
||||||
|
|
||||||
decl.add_method(sel!(windowWillClose:), will_close::<T> as extern fn(&Object, _, _));
|
decl.add_method(sel!(windowWillClose:), will_close::<T> as extern fn(&Object, _, _));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,4 @@ authors = ["Ryan McGrath <ryan@rymc.io>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
alchemy = { path = "../../alchemy", version = "0.2.0", features = ["cocoa"] }
|
alchemy = { path = "../../alchemy", version = "0.2.0", features = ["gtkrs"] }
|
||||||
|
|
|
||||||
|
|
@ -8,40 +8,22 @@
|
||||||
/// @created March 26th, 2019
|
/// @created March 26th, 2019
|
||||||
|
|
||||||
use alchemy::{
|
use alchemy::{
|
||||||
AppDelegate, Component, ComponentKey, Fragment, Error, Props, rsx, RSX, styles, text,
|
App, AppDelegate, Error, rsx,
|
||||||
Text, View, Window, WindowDelegate
|
RSX, text, Text, View, Window, WindowDelegate
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
window: Window
|
window: Option<Window>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppDelegate for AppState {
|
impl AppDelegate for AppState {
|
||||||
fn did_finish_launching(&mut self) {
|
fn did_finish_launching(&mut self) {
|
||||||
self.window.set_title("Layout Test");
|
let mut window = Window::new(WindowState {});
|
||||||
self.window.set_dimensions(100., 100., 600., 600.);
|
window.set_title("Layout Test");
|
||||||
self.window.show();
|
window.set_dimensions(100., 100., 600., 600.);
|
||||||
}
|
window.show();
|
||||||
}
|
self.window = Some(window);
|
||||||
|
println!("Should be showing");
|
||||||
#[derive(Default)]
|
|
||||||
struct BannerProps {}
|
|
||||||
|
|
||||||
#[derive(Props)]
|
|
||||||
struct Banner;
|
|
||||||
|
|
||||||
impl Component for Banner {
|
|
||||||
fn new(_key: ComponentKey) -> Banner {
|
|
||||||
Banner {}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&self, children: Vec<RSX>) -> Result<RSX, Error> {
|
|
||||||
Ok(rsx! {
|
|
||||||
<Fragment>
|
|
||||||
<View styles=["wut1"]></View>
|
|
||||||
{children}
|
|
||||||
</Fragment>
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,6 +36,7 @@ impl WindowDelegate for WindowState {
|
||||||
|
|
||||||
fn render(&self) -> Result<RSX, Error> {
|
fn render(&self) -> Result<RSX, Error> {
|
||||||
let messages = vec!["LOL"]; //, "wut", "BERT"];
|
let messages = vec!["LOL"]; //, "wut", "BERT"];
|
||||||
|
|
||||||
Ok(rsx! {
|
Ok(rsx! {
|
||||||
<View styles={&messages}>
|
<View styles={&messages}>
|
||||||
<Text styles=["message"]>"Hello there, my name is Bert"</Text>
|
<Text styles=["message"]>"Hello there, my name is Bert"</Text>
|
||||||
|
|
@ -61,64 +44,13 @@ impl WindowDelegate for WindowState {
|
||||||
{messages.iter().map(|message| rsx! {
|
{messages.iter().map(|message| rsx! {
|
||||||
<Text styles=["text"]>{text!("{}", message)}</Text>
|
<Text styles=["text"]>{text!("{}", message)}</Text>
|
||||||
})}
|
})}
|
||||||
<View styles=["box1"]>
|
|
||||||
<Banner>
|
|
||||||
<View styles=["innermostBox"] />
|
|
||||||
</Banner>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let app = alchemy::shared_app();
|
App::new(AppState {
|
||||||
|
window: None
|
||||||
app.register_styles("default", styles! {
|
}).run()
|
||||||
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 {
|
|
||||||
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
gtk/Cargo.toml
Normal file
19
gtk/Cargo.toml
Normal file
|
|
@ -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 <ryan@rymc.io>"]
|
||||||
|
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"
|
||||||
44
gtk/src/app.rs
Normal file
44
gtk/src/app.rs
Normal file
|
|
@ -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<T: AppDelegate + 'static>(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::<Vec<_>>());
|
||||||
|
}
|
||||||
|
}
|
||||||
22
gtk/src/lib.rs
Normal file
22
gtk/src/lib.rs
Normal file
|
|
@ -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;
|
||||||
47
gtk/src/text.rs
Normal file
47
gtk/src/text.rs
Normal file
|
|
@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
38
gtk/src/view.rs
Normal file
38
gtk/src/view.rs
Normal file
|
|
@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
73
gtk/src/window.rs
Normal file
73
gtk/src/window.rs
Normal file
|
|
@ -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<T: WindowDelegate>(content_view: (), app_ptr: *const RefCell<T>) -> 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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,10 +10,13 @@ categories = ["gui", "rendering::engine", "multimedia"]
|
||||||
keywords = ["gui", "css", "styles", "layout", "ui"]
|
keywords = ["gui", "css", "styles", "layout", "ui"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = []
|
||||||
cocoa = ["objc", "objc_id"]
|
cocoa = ["objc", "objc_id"]
|
||||||
|
gtkrs = ["gtk"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
alchemy-styles = { version = "0.1", path = "../styles" }
|
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 = { version = "0.2.6", optional = true }
|
||||||
objc_id = { version = "0.1.1", optional = true }
|
objc_id = { version = "0.1.1", optional = true }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ impl RenderEngine {
|
||||||
width: Dimension::Points(dimensions.0 as f32),
|
width: Dimension::Points(dimensions.0 as f32),
|
||||||
height: Dimension::Points(dimensions.1 as f32)
|
height: Dimension::Points(dimensions.1 as f32)
|
||||||
};
|
};
|
||||||
layout_store.set_style(layout, style);
|
layout_store.set_style(layout, style)?;
|
||||||
layout
|
layout
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@ use crate::rsx::RSX;
|
||||||
pub type PlatformSpecificNodeType = objc_id::ShareId<objc::runtime::Object>;
|
pub type PlatformSpecificNodeType = objc_id::ShareId<objc::runtime::Object>;
|
||||||
|
|
||||||
/// A per-platform wrapped Pointer type, used for attaching views/widgets.
|
/// A per-platform wrapped Pointer type, used for attaching views/widgets.
|
||||||
#[cfg(not(feature = "cocoa"))]
|
#[cfg(feature = "gtkrs")]
|
||||||
pub type PlatformSpecificNodeType = ();
|
pub type PlatformSpecificNodeType = ();
|
||||||
|
//pub type PlatformSpecificNodeType = gtk::WidgetExt;
|
||||||
|
|
||||||
/*fn update<C: Component, F: Fn() -> Box<C> + Send + Sync + 'static>(component: &Component, updater: F) {
|
/*fn update<C: Component, F: Fn() -> Box<C> + Send + Sync + 'static>(component: &Component, updater: F) {
|
||||||
let component_ptr = component as *const C as usize;
|
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.
|
/// 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
|
/// 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.
|
/// 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.
|
/// Fired when an Application is about to finish launching.
|
||||||
fn will_finish_launching(&mut self) {}
|
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
|
/// 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.
|
/// 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,
|
/// Fired when this Window will close. You can use this to clean up or destroy resources,
|
||||||
/// timers, and other things.
|
/// timers, and other things.
|
||||||
fn will_close(&mut self) { }
|
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.
|
/// lifecycle methods instead. Keeping `render()` pure makes components easier to think about.
|
||||||
///
|
///
|
||||||
/// This method is not called if should_component_update() returns `false`.
|
/// This method is not called if should_component_update() returns `false`.
|
||||||
fn render(&self, children: Vec<RSX>) -> Result<RSX, Error> { Ok(RSX::None) }
|
fn render(&self, _children: Vec<RSX>) -> Result<RSX, Error> { Ok(RSX::None) }
|
||||||
|
|
||||||
/// This lifecycle is invoked after an error has been thrown by a descendant component. It receives
|
/// 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.
|
/// the error that was thrown as a parameter and should return a value to update state.
|
||||||
|
|
|
||||||
Reference in a new issue