Initial, still major WIP

This commit is contained in:
Ryan McGrath 2018-06-19 23:55:57 -04:00
commit 0c503a549a
No known key found for this signature in database
GPG key ID: 811674B62B666830
21 changed files with 1302 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
certs
.env
target
**/*.rs.bk
**/*.swp

246
Cargo.lock generated Normal file
View file

@ -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"

16
Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "app"
version = "0.1.0"
authors = ["Ryan McGrath <ryan@rymc.io>"]
[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 = "*"

4
readme.md Normal file
View file

@ -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.

50
src/calendar.rs Normal file
View file

@ -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 <ryan@rymc.io>
//! @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<Data>,
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());
}*/
}
}

38
src/main.rs Normal file
View file

@ -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 <ryan@rymc.io>
//! @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")
])));
}

View file

@ -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 <ryan@rymc.io>
//! @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<String, Value>) {
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 {}

View file

@ -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 <ryan@rymc.io>
//! @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()];
}
}
}

46
src/shinekit/color.rs Normal file
View file

@ -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 <ryan@rymc.io>
/// @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] }
}
}

26
src/shinekit/layout.rs Normal file
View file

@ -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 <ryan@rymc.io>
//! @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<View>;
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) {
}

View file

@ -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 <ryan@rymc.io>
//! @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<T: TableViewData>(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<T: TableViewData>(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<T: TableViewData>() -> *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::<T> as extern fn(&Object, _, id) -> NSInteger);
decl.add_method(sel!(tableView:viewForTableColumn:row:), make_view::<T> as extern fn(&Object, _, id, id, NSInteger) -> id);
// Store internal state as user data
decl.add_ivar::<usize>("shinekitDataSourceAndDelegate");
delegate_class = decl.register();
});
unsafe {
delegate_class
}
}

View file

@ -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 <ryan@rymc.io>
//! @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<Object>,
pub scrollview: ScrollView,
delegate: Id<Object>
}
impl ListView {
pub fn new<T: TableViewData>(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::<T>(), 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
}
}

View file

@ -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 <ryan@rymc.io>
//! @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
}

31
src/shinekit/mod.rs Normal file
View file

@ -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 <ryan@rymc.io>
//! @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();
}

View file

@ -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 <ryan@rymc.io>
//! @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<Object>
}
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
}
}

View file

@ -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 <ryan@rymc.io>
// @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<String, Value> {
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
}

92
src/shinekit/text.rs Normal file
View file

@ -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 <ryan@rymc.io>
//! @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<Object>
}
#[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
}
}

28
src/shinekit/util.rs Normal file
View file

@ -0,0 +1,28 @@
//! util.rs
//!
//! Basic utility functions to handle some boilerplate things littered
//! throughout this app.
//!
//! @author Ryan McGrath <ryan@rymc.io>
//! @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();
}
}
}

218
src/shinekit/view.rs Normal file
View file

@ -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 <ryan@rymc.io>
//! @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<Object>,
pub subviews: Vec<View>
}
unsafe fn create_view_backing_node() -> Id<Object> {
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<View>) -> 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<String, Value>) {
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<View> { &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<id> = 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::<id>("shinekitBackgroundColor");
decl.add_ivar::<id>("shinekitConstraintWidth");
decl.add_ivar::<id>("shinekitConstraintHeight");
decl.add_ivar::<id>("shinekitConstraintTop");
decl.add_ivar::<id>("shinekitConstraintLeading");
decl.add_ivar::<id>("shinekitConstraintTrailing");
decl.add_ivar::<id>("shinekitConstraintBottom");
view_class = decl.register();
});
unsafe {
view_class
}
}

5
src/styles/dark.json Normal file
View file

@ -0,0 +1,5 @@
{
"calendar": {
"backgroundColor": "blue"
}
}

27
src/styles/default.json Normal file
View file

@ -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"
}
}