From 0c503a549a0c4071dbc6aa1dc58818f06dad44f9 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Tue, 19 Jun 2018 23:55:57 -0400 Subject: [PATCH] Initial, still major WIP --- .gitignore | 5 + Cargo.lock | 246 ++++++++++++++++++++++++++++ Cargo.toml | 16 ++ readme.md | 4 + src/calendar.rs | 50 ++++++ src/main.rs | 38 +++++ src/shinekit/application/mod.rs | 82 ++++++++++ src/shinekit/application/window.rs | 74 +++++++++ src/shinekit/color.rs | 46 ++++++ src/shinekit/layout.rs | 26 +++ src/shinekit/listview/datasource.rs | 93 +++++++++++ src/shinekit/listview/mod.rs | 97 +++++++++++ src/shinekit/listview/row.rs | 23 +++ src/shinekit/mod.rs | 31 ++++ src/shinekit/scrollview.rs | 61 +++++++ src/shinekit/stylesheet.rs | 40 +++++ src/shinekit/text.rs | 92 +++++++++++ src/shinekit/util.rs | 28 ++++ src/shinekit/view.rs | 218 ++++++++++++++++++++++++ src/styles/dark.json | 5 + src/styles/default.json | 27 +++ 21 files changed, 1302 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 readme.md create mode 100644 src/calendar.rs create mode 100644 src/main.rs create mode 100644 src/shinekit/application/mod.rs create mode 100644 src/shinekit/application/window.rs create mode 100644 src/shinekit/color.rs create mode 100644 src/shinekit/layout.rs create mode 100644 src/shinekit/listview/datasource.rs create mode 100644 src/shinekit/listview/mod.rs create mode 100644 src/shinekit/listview/row.rs create mode 100644 src/shinekit/mod.rs create mode 100644 src/shinekit/scrollview.rs create mode 100644 src/shinekit/stylesheet.rs create mode 100644 src/shinekit/text.rs create mode 100644 src/shinekit/util.rs create mode 100644 src/shinekit/view.rs create mode 100644 src/styles/dark.json create mode 100644 src/styles/default.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21ca23c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +certs +.env +target +**/*.rs.bk +**/*.swp diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e87b72b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,246 @@ +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "cocoa 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", + "im 10.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", + "objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "objc_id 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cocoa" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", + "objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "core-graphics" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dtoa" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "im" +version = "10.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "objc" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "objc_id" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_json" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" +"checksum block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum cocoa 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b44bd25bd275e9d74a5dff8ca55f2fb66c9ad5e12170d58697701df21a56e0e" +"checksum core-foundation 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7caa6cb9e76ddddbea09a03266d6b3bc98cd41e9fb9b017c473e7cca593ec25" +"checksum core-foundation-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b2a53cce0ddcf7e7e1f998738d757d5a3bf08bf799a180e50ebe50d298f52f5a" +"checksum core-graphics 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e54c4ab33705fa1fc8af375bb7929d68e1c1546c1ecef408966d8c3e49a1d84a" +"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" +"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum im 10.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "006e5c34d6fc287f91a90f53f19a8a859bade826e3153b137b8e1022ea54250b" +"checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682" +"checksum libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)" = "ac8ebf8343a981e2fa97042b14768f02ed3e1d602eac06cae6166df3c8ced206" +"checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +"checksum objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "877f30f37acef6749b1841cceab289707f211aecfc756553cd63976190e6cc2e" +"checksum objc_id 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4730aa1c64d722db45f7ccc4113a3e2c465d018de6db4d3e7dfe031e8c8a297" +"checksum rand 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a89abf8d34faf9783692392dca7bcdc6e82fa84eca86ccb6301ec87f3497185" +"checksum rand_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b7a5f27547c49e5ccf8a586db3f3782fd93cf849780b21853b9d981db203302" +"checksum rustc_version 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a54aa04a10c68c1c4eacb4337fd883b435997ede17a9385784b990777686b09a" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "e9a2d9a9ac5120e0f768801ca2b58ad6eec929dc9d1d616c162f208869c2ce95" +"checksum serde_json 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "93aee34bb692dde91e602871bc792dd319e489c7308cdbbe5f27cf27c64280f5" +"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ec12723 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "app" +version = "0.1.0" +authors = ["Ryan McGrath "] + +[dependencies] +libc = "0.2" +objc = "0.2.2" +core-foundation = "0.6.0" +core-graphics = "0.14.0" +cocoa = "0.15.0" +rand = "0.5.0" +im = "10.2.0" +objc_id = "0.1.0" +serde = "*" +serde_json = "*" diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..14dfaaa --- /dev/null +++ b/readme.md @@ -0,0 +1,4 @@ +# ShineKit +A project experimenting with implementing support for Cocoa/CocoaTouch in Rust. Also aims to support UWP on Windows by way of Microsoft's Objective-C project. + +Highly experimental, wouldn't touch this yet until an actual readme exists... unless you're brave. And enjoy the `unsafe` part of Rust at points. diff --git a/src/calendar.rs b/src/calendar.rs new file mode 100644 index 0000000..4b55e00 --- /dev/null +++ b/src/calendar.rs @@ -0,0 +1,50 @@ +//! calendar.rs +//! +//! The actual implementation of the calendar view/data/etc. Fetches +//! upcoming tournaments, ensures everything's good, and passes it on +//! demand to the rendering view(s). +//! +//! @author Ryan McGrath +//! @created 05/30/2018 + +use ruikit::view::View; +use ruikit::color::Color; +use ruikit::tableview::{TableViewData, TableViewRow}; + +#[derive(Debug)] +pub struct Data { + pub color: Color, + pub label: String +} + +impl Data { + pub fn new(msg: &str, r: i32, g: i32, b: i32) -> Self { + Data { + label: msg.to_string(), + color: Color::rgb(r,g,b) + } + } +} + +pub struct Calendar { + pub tournaments: Vec, + pub r: Color +} + +impl TableViewData for Calendar { + fn number_of_items(&self) -> usize { + self.tournaments.len() + } + + fn configure_item(&mut self, view: &TableViewRow, row: usize) { + println!("Hmmm... {}", row); + if row == 3 { + //view.set_background_color(&Color::system_red()); + view.set_background_color(&self.r); + }/* else if row == 2 { + view.set_background_color(&Color::rgb(5,35,229)); + } else { + view.set_background_color(&Color::system_blue()); + }*/ + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8f85a7d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,38 @@ +//! App +//! +//! An "App" in Rust, which is really just wrapping a lot of platform-specific +//! logic. Attempts to do as much as possible in Rust, however the GUI story in +//! Rust is the worst thing at the moment, so a lot of this is glorified message +//! passing to Objective C and co. ObjC is also one of the best languages ever +//! created and you can fight me on this if you so choose. +//! +//! @author Ryan McGrath +//! @created 05/30/2018 + +#![allow(dead_code)] +#![allow(non_upper_case_globals)] + +#[macro_use] +extern crate objc; +extern crate cocoa; +extern crate objc_id; +extern crate core_graphics; + +#[macro_use] +extern crate serde_json; + +// extern crate shinekit; +mod shinekit; +use shinekit::*; + +//mod calendar; +//use calendar::{Data, Calendar}; + +fn main() { + shinekit::run(vec![ + StyleSheet::default(include_str!("styles/default.json")) + ], App::new("eSports Calendar", View::named("root").subviews(vec![ + View::named("sidebar"), + View::named("content") + ]))); +} diff --git a/src/shinekit/application/mod.rs b/src/shinekit/application/mod.rs new file mode 100644 index 0000000..71f54e7 --- /dev/null +++ b/src/shinekit/application/mod.rs @@ -0,0 +1,82 @@ +//! application.rs +//! +//! Wraps application lifetime pieces across platforms. A lot of +//! unsafe code in here since it has to interact with a lot of +//! outside actors. With that said, if this crashes... there's bigger +//! problems with the system, lol. +//! +//! @author Ryan McGrath +//! @created 05/30/2018 + +use cocoa::base::{id, nil}; +use cocoa::foundation::{NSRect, NSPoint, NSSize, NSAutoreleasePool}; +use cocoa::appkit::{ + NSApp, NSApplication, NSApplicationActivationPolicyRegular, + NSRunningApplication, NSApplicationActivateIgnoringOtherApps +}; + +use serde_json::{Map, Value}; + +pub mod window; +use shinekit::color::Color; +use shinekit::application::window::Window; +use shinekit::view::View; + +pub struct App { + pub app: id, + pub window: Window, +} +/* +pub trait Delegate { + // AppDelegate lifecycle callbacks + fn did_finish_launching(&self) {} + fn will_become_active(&self) {} + fn will_enter_foreground(&self) {} + fn did_become_active(&self) {} + fn will_resign_active(&self) {} + fn did_enter_background(&self) {} + fn will_terminate(&self) {} + + // Override-able settings + fn initial_window_rect(&self) -> (f64, f64, f64, f64) { (0., 0., 800., 600.) } + fn window_mask(&self) -> NSWindowStyleMask { + } +}*/ + +impl App { + pub fn new(title: &str, view: View) -> Self { + App { + app: unsafe { + let _pool = NSAutoreleasePool::new(nil); + let app = NSApp(); + app.setActivationPolicy_(NSApplicationActivationPolicyRegular); + app + }, + + window: Window::new(view, title, 0, 0, 0, 0) + } + } + + pub fn run(&self) { + unsafe { + let current_app = NSRunningApplication::currentApplication(nil); + current_app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps); + self.window.make_key(); + self.app.run(); + } + } + + pub fn apply_styles(&mut self, styles: &mut Map) { + let bg_color = Color::from_json(&styles["window"]["backgroundColor"]); + self.window.set_background_color(bg_color); + + let width = &styles["window"]["defaultWidth"].as_f64().unwrap(); + let height = &styles["window"]["defaultHeight"].as_f64().unwrap(); + let rect = NSRect::new(NSPoint::new(0., 0.), NSSize::new(*width, *height)); + self.window.set_frame(rect); + + self.window.content_view.apply_styles(styles); + } +} + +//impl Delegate for App {} diff --git a/src/shinekit/application/window.rs b/src/shinekit/application/window.rs new file mode 100644 index 0000000..370e5c7 --- /dev/null +++ b/src/shinekit/application/window.rs @@ -0,0 +1,74 @@ +//! window.rs +//! +//! Handles creating/maintaining/applying styles for the root backing window of +//! an app. Very Desktop dependent, as mobile apps... well, they're just in a +//! window by default. +//! +//! @author Ryan McGrath +//! @created 05/30/2018 + +use cocoa::base::{id, nil, YES, NO}; +use cocoa::appkit::{NSWindow, NSWindowStyleMask, NSBackingStoreType}; +use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString, NSAutoreleasePool}; + +use shinekit::color::Color; +use shinekit::view::View; + +pub struct Window { + pub window: id, + pub content_view: View +} + +impl Window { + pub fn new(view: View, title: &str, top: i32, left: i32, width: i32, height: i32) -> Self { + Window { + window: unsafe { + let style = NSWindowStyleMask::NSResizableWindowMask | + NSWindowStyleMask::NSUnifiedTitleAndToolbarWindowMask | NSWindowStyleMask::NSMiniaturizableWindowMask | + NSWindowStyleMask::NSClosableWindowMask | NSWindowStyleMask::NSTitledWindowMask; + //NSWindowStyleMask::NSWindowStyleMaskResizable | NSWindowStyleMask::NSWindowStyleMaskMiniaturizable | NSWindowStyleMask::NSWindowStyleMaskClosable | NSWindowStyleMask::NSWindowStyleMaskTitled | NSWindowStyleMask::NSWindowStyleMaskUnifiedTitleAndToolbar + + let window = NSWindow::alloc(nil).initWithContentRect_styleMask_backing_defer_( + NSRect::new(NSPoint::new(top.into(), left.into()), NSSize::new(width.into(), height.into())), + style, + NSBackingStoreType::NSBackingStoreBuffered, + NO + ).autorelease(); + + let title = NSString::alloc(nil).init_str(title); + window.setTitle_(title); + view.translates_resizing_mask_into_constraints(true); + msg_send![window, setContentView:view.get_root_backing_node()]; + msg_send![window, setTitlebarAppearsTransparent:YES]; + msg_send![window, setTitleVisibility:1]; + window + }, + content_view: view, + } + } + + pub fn make_key(&self) { + unsafe { + self.window.makeKeyAndOrderFront_(nil); + } + } + + /*pub fn set_content_view(&mut self, view: &T) { + unsafe { + view.translates_resizing_mask_into_constraints(true); + msg_send![self.window, setContentView:&*view.get_root_backing_node()]; + } + }*/ + + pub fn set_frame(&self, rect: NSRect) { + unsafe { + msg_send![self.window, setFrame:rect display:YES]; + } + } + + pub fn set_background_color(&self, color: Color) { + unsafe { + msg_send![self.window, setBackgroundColor:color.into_platform_specific_color()]; + } + } +} diff --git a/src/shinekit/color.rs b/src/shinekit/color.rs new file mode 100644 index 0000000..1426d32 --- /dev/null +++ b/src/shinekit/color.rs @@ -0,0 +1,46 @@ +/// +/// Color.rs +/// +/// Interface that wraps [NS/UI]Color depending on the platform. Decidedly +/// basic as I don't care to get into the whole colorspace issue, and would +/// rather just be able to put color on a screen. +/// +/// @author Ryan McGrath +/// @created 05/23/2018 +/// + +use cocoa::base::{id, class}; +use serde_json::Value; + +#[derive(Debug)] +pub struct Color { + pub r: f64, + pub g: f64, + pub b: f64, + pub a: f64 +} + +#[allow(dead_code)] +impl Color { + pub fn rgb(r: i32, g: i32, b: i32) -> Self { + Color { + r: r as f64 / 255.0, + g: g as f64 / 255.0, + b: b as f64 / 255.0, + a: 1. + } + } + + pub fn from_json(obj: &Value) -> Self { + Color { + r: obj["r"].as_f64().unwrap() / 255.0, + g: obj["g"].as_f64().unwrap() / 255.0, + b: obj["b"].as_f64().unwrap() / 255.0, + a: 1. + } + } + + pub fn into_platform_specific_color(&self) -> id { + unsafe { msg_send![class("NSColor"), colorWithRed:self.r green:self.g blue:self.b alpha:self.a] } + } +} diff --git a/src/shinekit/layout.rs b/src/shinekit/layout.rs new file mode 100644 index 0000000..dd0a584 --- /dev/null +++ b/src/shinekit/layout.rs @@ -0,0 +1,26 @@ +//! layout.rs +//! +//! A trait that more or less allows any other types to compose +//! a "subclass" for a view. By using the actual backing [UI|NS]View +//! for storing certain properties, it becomes much easier to get around +//! Rust's (good-for-you) restrictions. +//! +//! @author Ryan McGrath +//! @created 05/30/2018 + +use objc::declare::ClassDecl; +use objc::runtime::{Object, BOOL}; +use cocoa::base::{class, id, nil, YES, NO}; +use cocoa::foundation::NSArray; + +use shinekit::view::View; + +pub trait Layout { + fn get_subviews(&self) -> &Vec; + fn get_root_backing_node(&self) -> &Object; + fn set_constraint_ivar(&mut self, ivar: &str, constraint: id); + +} + +pub fn add_autolayout_ivars(decl: &mut ClassDecl) { +} diff --git a/src/shinekit/listview/datasource.rs b/src/shinekit/listview/datasource.rs new file mode 100644 index 0000000..078c54e --- /dev/null +++ b/src/shinekit/listview/datasource.rs @@ -0,0 +1,93 @@ +//! tableview_delegate_internal.rs +//! +//! This is a struct that handles the nitty gritty implementation of [NS/UI]TableViews. +//! These are classes that have a lot of delegate-based implementation, so this is +//! essentially bridging that gap for Rust. +//! +//! @author Ryan McGrath +//! @created 05/30/2018 + +use std::sync::{Once, ONCE_INIT}; + +use objc::declare::ClassDecl; +use objc::runtime::{Class, Object, Sel}; +use cocoa::base::{id, nil, NO, YES}; +use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString, NSInteger}; + +use shinekit::tableview::row::TableViewRow; +use shinekit::view::register_view_class; + +pub trait TableViewData { + fn number_of_items(&self) -> usize { 0 } + fn configure_item(&mut self, view: &TableViewRow, row: usize); +} + +extern fn number_of_items(this: &Object, _: Sel, _: id) -> NSInteger { + println!("number_of_items"); + let count: usize; + + unsafe { + let state: usize = *this.get_ivar("shinekitDataSourceAndDelegate"); + let state = state as *mut T; + count = (*state).number_of_items(); + } + + return count as NSInteger; +} + +extern fn make_view(this: &Object, _: Sel, table_view: id, _: id, row: NSInteger) -> id { + println!("make_view"); + let mut cell: id; + let view: TableViewRow; + let mut requires_layout = false; + + unsafe { + let state: usize = *this.get_ivar("shinekitgSourceAndDelegate"); + let state = state as *mut T; + + let title = NSString::alloc(nil).init_str("WHAT"); + cell = msg_send![table_view, makeViewWithIdentifier:title owner:nil]; + if cell == nil { + println!(" Creating new cell..."); + requires_layout = true; + let default_frame = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)); + let cls: id = msg_send![register_view_class(), alloc]; + cell = msg_send![cls, initWithFrame:default_frame]; + msg_send![cell, setIdentifier:title]; + msg_send![cell, setWantsLayer:YES]; + msg_send![cell, setTranslatesAutoresizingMaskIntoConstraints:NO]; + } + + view = TableViewRow { + row: row as usize, + view: cell + }; + println!("Configuring cell..."); + (*state).configure_item(&view, row as usize); + println!("Configured Cell"); + } + + return cell; +} + +pub fn register_listview_delegate_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("shinekitDataSourceAndDelegate", superclass).unwrap(); + + // Add callback methods + decl.add_method(sel!(numberOfRowsInTableView:), number_of_items:: as extern fn(&Object, _, id) -> NSInteger); + decl.add_method(sel!(tableView:viewForTableColumn:row:), make_view:: as extern fn(&Object, _, id, id, NSInteger) -> id); + + // Store internal state as user data + decl.add_ivar::("shinekitDataSourceAndDelegate"); + delegate_class = decl.register(); + }); + + unsafe { + delegate_class + } +} diff --git a/src/shinekit/listview/mod.rs b/src/shinekit/listview/mod.rs new file mode 100644 index 0000000..f184352 --- /dev/null +++ b/src/shinekit/listview/mod.rs @@ -0,0 +1,97 @@ +//! table.rs +//! +//! Wraps TableView class(es) on supported platforms. On macOS (OS X!) it wraps NSTableView, +//! and where possible on other platforms it wraps UITableView (Windows, for instance). In +//! either case this wraps the underlying APIs to mimic each other and remove nuisances that +//! exist. +//! +//! Many might ask "Why not \*CollectionView?", and to be fair, it's not a bad question. The +//! TableView APIs tend to be much better and smoother with regards to auto-calculating view +//! heights for dynamic entries, so... I just go with those when possible. Less headache. +//! +//! @author Ryan McGrath +//! @created 05/23/2018 + +use objc_id::Id; +use cocoa::base::{class, id, nil, NO, YES}; +use cocoa::foundation::NSString; + +pub mod row; +pub mod datasource; +use shinekit::view::View; +use shinekit::scrollview::ScrollView; +use shinekit::util::empty_frame; +use shinekit::tableview::datasource::register_delegate_class; +pub use shinekit::tableview::row::{TableViewRow, TableViewUI}; +pub use shinekit::tableview::datasource::TableViewData; + +pub struct ListView { + pub view: Id, + pub scrollview: ScrollView, + delegate: Id +} + +impl ListView { + pub fn new(datasource: T) -> Self { + let tableview: id; + let scrollview = ScrollView::new(); + let delegate: id; + + unsafe { + let cls: id = msg_send![register_list_class(), alloc]; + tableview = msg_send![cls, initWithFrame:empty_frame()]; + msg_send![tableview, setWantsLayer:YES]; + msg_send![tableview, setTranslatesAutoresizingMaskIntoConstraints:NO]; + msg_send![tableview, setUsesAutomaticRowHeights:YES]; + msg_send![tableview, setRowHeight:100.]; + msg_send![tableview, setFloatsGroupRows:YES]; + msg_send![tableview, setIntercellSpacing:NSSize::new(0., 0.)]; + msg_send![tableview, setColumnAutoresizingStyle:1]; + msg_send![tableview, setUsesAlternatingRowBackgroundColors:NO]; + //msg_send![tableview, setSelectionHighlightStyle:-1]; + msg_send![tableview, setAllowsEmptySelection:YES]; + msg_send![tableview, setAllowsMultipleSelection:NO]; + msg_send![tableview, setHeaderView:nil]; + + // NSTableView requires at least one column to be manually added if doing so by code. + // A relic of a bygone era, indeed. + let default_column_alloc: id = msg_send![class("NSTableColumn"), new]; + let default_column: id = msg_send![default_column_alloc, initWithIdentifier:NSString::alloc(nil).init_str("Wut")]; + msg_send![default_column, setResizingMask:(1<<0)]; + msg_send![tableview, addTableColumn:default_column]; + + delegate = msg_send![register_delegate_class::(), new]; + //msg_send![scrollview, setDocumentView:tableview]; + } + + let mut state = Box::new(datasource); + let state_ptr: *mut T = &mut *state; + unsafe { + (&mut *delegate).set_ivar("shinekit_tableview_datasource", state_ptr as usize); + msg_send![tableview, setDelegate:delegate]; + msg_send![tableview, setDataSource:delegate]; + } + + TableView { + view: IdRef::new(tableview), + scrollview: IdRef::new(scrollview), + delegate: IdRef::new(delegate) + } + } +} + +fn register_list_class() -> *const Class { + static mut list_class: *const Class = 0 as *const Class; + static INIT: Once = ONCE_INIT; + + INIT.call_once(|| unsafe { + let superclass = Class::get("NSTableView").unwrap(); + let mut decl = ClassDecl::new("shinekitListView", superclass).unwrap(); + add_autolayout_ivars(&mut decl); + list_class = decl.register(); + }); + + unsafe { + list_class + } +} diff --git a/src/shinekit/listview/row.rs b/src/shinekit/listview/row.rs new file mode 100644 index 0000000..304afab --- /dev/null +++ b/src/shinekit/listview/row.rs @@ -0,0 +1,23 @@ +//! row.rs +//! +//! A default implementation for a TableView row, which... well, depending +//! on the platform and/or environment, requires a few different things. This +//! ensures that we at least have the following: +//! +//! - Properly flipped coordinates, because it's {CURRENT_YEAR} and who +//! the hell judges from the bottom left. Confuses all newcomers. +//! +//! @author Ryan McGrath +//! @created 05/30/2018 + +use cocoa::base::{id}; + +pub trait TableViewUI { + fn layout(&self, view: &TableViewRow); + fn update(&self, view: &TableViewRow); +} + +pub struct TableViewRow { + pub row: usize, + pub view: id +} diff --git a/src/shinekit/mod.rs b/src/shinekit/mod.rs new file mode 100644 index 0000000..f5038ed --- /dev/null +++ b/src/shinekit/mod.rs @@ -0,0 +1,31 @@ +//! mod.rs +//! +//! Shinekit main module, which is basically handling various things +//! like ensuring styles are registered, the App starts running, and +//! everything is properly resolved and installed. +//! +//! @author Ryan McGrath +//! @created 05/30/2018 + +pub mod application; +//pub mod tableview; +pub mod color; +//pub mod scrollview; +//pub mod text; +pub mod util; +pub mod view; +pub mod stylesheet; + +pub use application::App; +pub use stylesheet::StyleSheet; +pub use view::View; + +use serde_json::Value; +use stylesheet::load_styles; + +pub fn run(user_styles: Vec<(String, Value)>, mut application: App) { + let mut styles = load_styles(user_styles); + let mut current_style = styles["default"].as_object_mut().unwrap(); + application.apply_styles(&mut current_style); + application.run(); +} diff --git a/src/shinekit/scrollview.rs b/src/shinekit/scrollview.rs new file mode 100644 index 0000000..bd809bd --- /dev/null +++ b/src/shinekit/scrollview.rs @@ -0,0 +1,61 @@ +//! scrollview.rs +//! +//! Handles making NSScrollView behave more like a UIKit counterpart. Wraps +//! up various methods to make it more... enjoyable to use. +//! +//! @author Ryan McGrath +//! @created 05/30/2018 + +use std::sync::{Once, ONCE_INIT}; + +use objc_id::Id; +use objc::declare::ClassDecl; +use objc::runtime::{Class, Object}; + +use cocoa::base::{id, YES, NO}; + +use shinekit::util::empty_frame; +use shinekit::layout::{Layout, add_autolayout_ivars}; + +pub struct ScrollView { + backing_node: Id +} + +impl ScrollView { + pub fn new() -> Self { + ScrollView { + backing_node: unsafe { + let ins: id = msg_send![register_scrollview_class(), alloc]; + let scrollview: id = msg_send![ins, initWithFrame:empty_frame()]; + msg_send![scrollview, setTranslatesAutoresizingMaskIntoConstraints:NO]; + msg_send![scrollview, setDrawsBackground:NO]; + msg_send![scrollview, setWantsLayer:YES]; + msg_send![scrollview, setBorderType:0]; + msg_send![scrollview, setHorizontalScrollElasticity:1]; + msg_send![scrollview, setHasVerticalScroller:YES]; + Id::from_ptr(scrollview) + } + } + } +} + +impl Layout for ScrollView { + fn get_root_backing_node(&self) -> &Object { &*self.backing_node } + fn set_constraint_ivar(&mut self, ivar: &str, constraint: id) { unsafe { self.backing_node.set_ivar(ivar, constraint); } } +} + +fn register_scrollview_class() -> *const Class { + static mut scrollview_class: *const Class = 0 as *const Class; + static INIT: Once = ONCE_INIT; + + INIT.call_once(|| unsafe { + let superclass = Class::get("NSScrollView").unwrap(); + let mut decl = ClassDecl::new("shinekitScrollView", superclass).unwrap(); + add_autolayout_ivars(&mut decl); + scrollview_class = decl.register(); + }); + + unsafe { + scrollview_class + } +} diff --git a/src/shinekit/stylesheet.rs b/src/shinekit/stylesheet.rs new file mode 100644 index 0000000..caf5ceb --- /dev/null +++ b/src/shinekit/stylesheet.rs @@ -0,0 +1,40 @@ +// stylesheet.rs +// +// A basic struct for implementing namespaced stylesheets. Useful +// so themes are built-in from the beginning. Currently mostly a +// fun little JSON based thing, but I could see this being made +// better in the future. +// +// @author Ryan McGrath +// @created 05/30/2018 + +use serde_json::{Map, Value, from_str}; +use util::merge_json_values; + +pub struct StyleSheet {} + +impl StyleSheet { + pub fn default(styles: &str) -> (String, Value) { + ("default".into(), from_str(styles).expect("Could not parse default JSON stylesheet")) + } + + pub fn theme(name: &str, styles: &str) -> (String, Value) { + (name.into(), from_str(styles).expect(&format!("Could not parse {} stylesheet", name))) + } +} + +pub fn load_styles(user_styles: Vec<(String, Value)>) -> Map { + let mut styles = Map::new(); + for (name, value) in user_styles.into_iter() { + if styles.contains_key("default") { + let mut style = json!({}); + merge_json_values(&mut style, &styles["default"]); + merge_json_values(&mut style, &value); + styles.insert(name, style); + } else { + styles.insert(name, value); + } + } + + styles +} diff --git a/src/shinekit/text.rs b/src/shinekit/text.rs new file mode 100644 index 0000000..603aaf0 --- /dev/null +++ b/src/shinekit/text.rs @@ -0,0 +1,92 @@ +//! text.rs +//! +//! A class that wraps NSTextField and/or UILabel to make them act pretty +//! much the same across platforms. Believe it or not... this is a thing. +//! +//! @author Ryan McGrath +//! @created 05/30/2018 + +use std::sync::{Once, ONCE_INIT}; + +use objc_id::Id; +use objc::declare::ClassDecl; +use objc::runtime::{Class, Object}; + +use cocoa::base::{id, nil, YES, NO}; +use cocoa::foundation::NSString; + +use shinekit::color::Color; +use shinekit::util::empty_frame; +use shinekit::layout::{Layout, add_autolayout_ivars}; + +pub struct Text { + pub backing_node: Id +} + +#[allow(dead_code)] +impl Text { + pub fn new() -> Self { + Text { + backing_node: unsafe { + let alloc: id = msg_send![register_text_class(), alloc]; + let view: id = msg_send![alloc, initWithFrame:empty_frame()]; + msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; + msg_send![view, setEditable:NO]; + msg_send![view, setBezeled:NO]; + msg_send![view, setBordered:NO]; + msg_send![view, setDrawsBackground:YES]; + msg_send![view, setAllowsEditingTextAttributes:NO]; + msg_send![view, setContentCompressionResistancePriority:250 forOrientation:0]; + + let cell: id = msg_send![view, cell]; + msg_send![cell, setUsesSingleLineMode:NO]; + msg_send![cell, setWraps:YES]; + msg_send![cell, setLineBreakMode:0]; + Id::from_ptr(view) + } + } + } + + pub fn set_background_color(&mut self, color: &Color) { + unsafe { + self.backing_node.set_ivar("shinekitBackgroundColor", color.into_platform_specific_color()); + msg_send![&*self.backing_node, setNeedsDisplay:YES]; + } + } + + pub fn set_text(&self, text: &str) { + unsafe { + let value = NSString::alloc(nil).init_str(text); + msg_send![&*self.backing_node, setStringValue:value]; + } + } + + pub fn set_text_color(&self, color: &Color) { + unsafe { + msg_send![&*self.backing_node, setTextColor:color.into_platform_specific_color()]; + } + } +} + +impl Layout for Text { + fn get_root_backing_node(&self) -> &Object { &*self.backing_node } + fn set_constraint_ivar(&mut self, ivar: &str, constraint: id) { unsafe { self.backing_node.set_ivar(ivar, constraint); } } +} + +fn register_text_class() -> *const Class { + static mut text_class: *const Class = 0 as *const Class; + static INIT: Once = ONCE_INIT; + + INIT.call_once(|| unsafe { + let superclass = Class::get("NSTextField").unwrap(); + let mut decl = ClassDecl::new("shinekitTextLabel", superclass).unwrap(); + //decl.add_method(sel!(wantsUpdateLayer), enforce_normalcy as extern fn(&Object, _) -> BOOL); + //decl.add_method(sel!(updateLayer), update_layer as extern fn(&Object, _)); + add_autolayout_ivars(&mut decl); + text_class = decl.register(); + }); + + unsafe { + text_class + } +} diff --git a/src/shinekit/util.rs b/src/shinekit/util.rs new file mode 100644 index 0000000..39f9858 --- /dev/null +++ b/src/shinekit/util.rs @@ -0,0 +1,28 @@ +//! util.rs +//! +//! Basic utility functions to handle some boilerplate things littered +//! throughout this app. +//! +//! @author Ryan McGrath +//! @created 05/30/2018 + +use serde_json::{Value}; +use cocoa::foundation::{NSRect, NSPoint, NSSize}; + +pub fn empty_frame() -> NSRect { + NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)) +} + +pub fn merge_json_values(a: &mut Value, b: &Value) { + match (a, b) { + (&mut Value::Object(ref mut a), &Value::Object(ref b)) => { + for (k, v) in b { + merge_json_values(a.entry(k.clone()).or_insert(Value::Null), v); + } + } + + (a, b) => { + *a = b.clone(); + } + } +} diff --git a/src/shinekit/view.rs b/src/shinekit/view.rs new file mode 100644 index 0000000..d1d2bec --- /dev/null +++ b/src/shinekit/view.rs @@ -0,0 +1,218 @@ +//! view.rs +//! +//! "Fixes" NSView to be a bit more... how does one say, modern. Flips drawing +//! and layout coordinates to be fitting for {{CURRENT YEAR}}, layer-backs it all +//! by default, and does some ivar trickery to make NSColor less of a headache. +//! +//! @author Ryan McGrath +//! @created 05/30/2018 + +use std::sync::{Once, ONCE_INIT}; +use serde_json::{Map, Value}; + +use objc_id::Id; +use objc::declare::ClassDecl; +use objc::runtime::{Class, Object, Sel, BOOL}; +use cocoa::foundation::NSArray; +use cocoa::base::{class, id, nil, YES, NO}; + +use shinekit::color::Color; +use shinekit::util::empty_frame; + +#[derive(Debug)] +pub enum ViewKind { + View, + Label +} + +#[derive(Debug)] +pub struct View { + pub kind: ViewKind, + pub name: String, + pub backing_node: Id, + pub subviews: Vec +} + +unsafe fn create_view_backing_node() -> Id { + let alloc: id = msg_send![register_view_class(), alloc]; + let view: id = msg_send![alloc, initWithFrame:empty_frame()]; + msg_send![view, setWantsLayer:YES]; + msg_send![view, setLayerContentsRedrawPolicy:1]; + msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; + Id::from_ptr(view) +} + +impl View { + pub fn named(name: &str) -> Self { + View { + name: name.into(), + kind: ViewKind::View, + backing_node: unsafe { create_view_backing_node() }, + subviews: vec![] + } + } + + pub fn subviews(self, views: Vec) -> Self { + let mut subviews = vec![]; + unsafe { + for view in views.into_iter() { + msg_send![&*self.backing_node, addSubview:&*view.backing_node]; + subviews.push(view); + } + } + + View { + subviews: subviews, + ..self + } + } + + pub fn set_background_color(&mut self, color: &Color) { + unsafe { + self.backing_node.set_ivar("shinekitBackgroundColor", color.into_platform_specific_color()); + msg_send![&*self.backing_node, setNeedsDisplay:YES]; + } + } + + pub fn apply_styles(&mut self, styles: &mut Map) { + let bg_color = Color::from_json(&styles[&self.name]["backgroundColor"]); + self.set_background_color(&bg_color); + + for view in &mut self.subviews { + view.apply_styles(styles); + } + } + + pub fn get_root_backing_node(&self) -> &Object { &*self.backing_node } + pub fn get_subviews(&self) -> &Vec { &self.subviews } + pub fn set_constraint_ivar(&mut self, ivar: &str, constraint: id) { unsafe { self.backing_node.set_ivar(ivar, constraint); } } + + pub fn add_subview(&self, view: &View) { + unsafe { + msg_send![self.get_root_backing_node(), addSubview:view.get_root_backing_node()]; + } + } + + pub fn translates_resizing_mask_into_constraints(&self, translates: bool) { + let t: BOOL = if translates { YES } else { NO }; + unsafe { + msg_send![self.get_root_backing_node(), setTranslatesAutoresizingMaskIntoConstraints:t]; + } + } + + pub fn width(&mut self, width: i32) { + unsafe { + let anchor: id = msg_send![self.get_root_backing_node(), widthAnchor]; + let constraint: id = msg_send![anchor, constraintEqualToConstant:width as f64]; + self.set_constraint_ivar("shinekitConstraintWidth", constraint); + } + } + + pub fn height(&mut self, height: i32) { + unsafe { + let anchor: id = msg_send![self.get_root_backing_node(), heightAnchor]; + let constraint: id = msg_send![anchor, constraintEqualToConstant:height as f64]; + self.set_constraint_ivar("shinekitConstraintHeight", constraint); + } + } + + pub fn top_relative_to(&mut self, view: &View, margin: i32) { + unsafe { + let top_anchor: id = msg_send![self.get_root_backing_node(), topAnchor]; + let view_top_anchor: id = msg_send![view.get_root_backing_node(), topAnchor]; + let constraint: id = msg_send![top_anchor, constraintEqualToAnchor:view_top_anchor constant:margin as f64]; + self.set_constraint_ivar("shinekitConstraintTop", constraint); + } + } + + pub fn leading_relative_to(&mut self, view: &View, margin: i32) { + unsafe { + let leading_anchor: id = msg_send![self.get_root_backing_node(), leadingAnchor]; + let view_leading_anchor: id = msg_send![view.get_root_backing_node(), leadingAnchor]; + let constraint: id = msg_send![leading_anchor, constraintEqualToAnchor:view_leading_anchor constant:margin as f64]; + self.set_constraint_ivar("shinekitConstraintLeading", constraint); + } + } + + pub fn trailing_relative_to(&mut self, view: &View, margin: i32) { + let m = margin as f64 * -1.; + unsafe { + let trailing_anchor: id = msg_send![self.get_root_backing_node(), trailingAnchor]; + let view_trailing_anchor: id = msg_send![view.get_root_backing_node(), trailingAnchor]; + let constraint: id = msg_send![trailing_anchor, constraintEqualToAnchor:view_trailing_anchor constant:m]; + self.set_constraint_ivar("shinekitConstraintTrailing", constraint); + } + } + + pub fn bottom_relative_to(&mut self, view: &View, margin: i32) { + let m = margin as f64 * -1.; + unsafe { + let bottom_anchor: id = msg_send![self.get_root_backing_node(), bottomAnchor]; + let view_bottom_anchor: id = msg_send![view.get_root_backing_node(), bottomAnchor]; + let constraint: id = msg_send![bottom_anchor, constraintEqualToAnchor:view_bottom_anchor constant:m]; + self.set_constraint_ivar("shinekitConstraintBottom", constraint); + } + } + + pub fn activate_constraints(&self) { + unsafe { + let mut anchors: Vec = vec![]; + + let ivars = [ + "shinekitConstraintWidth", "shinekitConstraintHeight", + "shinekitConstraintTop", "shinekitConstraintLeading", + "shinekitConstraintTrailing", "shinekitConstraintBottom" + ]; + + let view = self.get_root_backing_node(); + for ivar in &ivars { + let constraint: id = *view.get_ivar(ivar); + if constraint != nil { anchors.push(constraint); } + } + + let constraints = NSArray::arrayWithObjects(nil, &anchors); + msg_send![class("NSLayoutConstraint"), activateConstraints:constraints]; + } + } +} + +extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL { + return YES; +} + +extern fn update_layer(this: &Object, _: Sel) { + unsafe { + let background_color: id = *this.get_ivar("shinekitBackgroundColor"); + if background_color != nil { + let layer: id = msg_send![this, layer]; + let cg: id = msg_send![background_color, CGColor]; + msg_send![layer, setBackgroundColor:cg]; + } + } +} + +fn register_view_class() -> *const Class { + static mut view_class: *const Class = 0 as *const Class; + static INIT: Once = ONCE_INIT; + + INIT.call_once(|| unsafe { + let superclass = Class::get("NSView").unwrap(); + let mut decl = ClassDecl::new("shinekitView", superclass).unwrap(); + decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL); + decl.add_method(sel!(requiresConstraintBasedLayout), enforce_normalcy as extern fn(&Object, _) -> BOOL); + decl.add_method(sel!(wantsUpdateLayer), enforce_normalcy as extern fn(&Object, _) -> BOOL); + decl.add_method(sel!(updateLayer), update_layer as extern fn(&Object, _)); + decl.add_ivar::("shinekitBackgroundColor"); + decl.add_ivar::("shinekitConstraintWidth"); + decl.add_ivar::("shinekitConstraintHeight"); + decl.add_ivar::("shinekitConstraintTop"); + decl.add_ivar::("shinekitConstraintLeading"); + decl.add_ivar::("shinekitConstraintTrailing"); + decl.add_ivar::("shinekitConstraintBottom"); + view_class = decl.register(); + }); + + unsafe { + view_class + } +} diff --git a/src/styles/dark.json b/src/styles/dark.json new file mode 100644 index 0000000..bdb8f33 --- /dev/null +++ b/src/styles/dark.json @@ -0,0 +1,5 @@ +{ + "calendar": { + "backgroundColor": "blue" + } +} diff --git a/src/styles/default.json b/src/styles/default.json new file mode 100644 index 0000000..7a93b4c --- /dev/null +++ b/src/styles/default.json @@ -0,0 +1,27 @@ +{ + "window": { + "backgroundColor": {"r": 35, "g": 108, "b": 218}, + "defaultWidth": 800, + "defaultHeight": 600 + }, + + "root": { + "backgroundColor": {"r": 35, "g": 108, "b": 218} + }, + + "sidebar": { + "backgroundColor": {"r": 5, "g": 5, "b": 5}, + "width": 200, + "top": "root.top", + "left": "root.left", + "bottom": "root.bottom" + }, + + "content": { + "backgroundColor": {"r": 35, "g": 108, "b": 218}, + "top": "root.top", + "left": "sidebar.right", + "right": "root.right", + "bottom": "root.bottom" + } +}