Begin reworking many internals to push for v0.1.
- Beginning to transition View types to use Rc/RefCell internally, which should provide better guarantees about ownership on the Rust side. This is also important for certain Objective-C side scenarios where we may need to set an ivar after creation, which requires some level of mutability. This may also possibly help bring down the unsafe usage, which would be cool. - Rewrote the Color module; this now handles system colors better, and provides support for dynamic color creation. Supporting combinations of dark/light/contrast is now possible with handler passed in via `Color::dynamic`. This still has work to do in terms of some accessor methods and such, but it works well for now. The `to_platform...` method should be removed before v0.1. - Added a new feature for enabling fallback color usage on older macOS versions that don't support system colors. This may honestly never be used, but it cost nothing to implement. - Fixed a bug in the Autolayout wrapper where dereferencing could cause constraints to crash at runtime. - Support setting text color on labels. - Support setting text color on buttons, albeit very hacky right now. This needs to be extracted and/or cleaned up, but getting it sketched out was important for this commit. - Support setting a key equivalent on buttons. - Creating a local event monitor is now possible. - Examples updated; Calculator clone example added. The only API breaking change in this commit from earlier commits should be `color::rgb` needing to be `color::Color` followed by a `Color::rgb(...)` call.
This commit is contained in:
parent
9511a5a82c
commit
5cd59b5636
|
@ -25,11 +25,15 @@ os_info = "3.0.1"
|
|||
uuid = { version = "0.8", features = ["v4"], optional = true }
|
||||
url = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
eval = "0.4"
|
||||
|
||||
[features]
|
||||
default = ["macos"]
|
||||
cloudkit = []
|
||||
ios = []
|
||||
macos = []
|
||||
color_fallbacks = []
|
||||
quicklook = []
|
||||
user-notifications = ["uuid"]
|
||||
webview = []
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! This example showcases setting up a basic application and window, and setting up some views to
|
||||
//! work with autolayout.
|
||||
//! This example showcases setting up a basic application and window, setting up some views to
|
||||
//! work with autolayout, and some basic ways to handle colors.
|
||||
|
||||
use cacao::color::rgb;
|
||||
use cacao::color::{Color, Theme};
|
||||
use cacao::layout::{Layout, LayoutConstraint};
|
||||
use cacao::view::View;
|
||||
|
||||
|
@ -24,8 +24,7 @@ struct AppWindow {
|
|||
content: View,
|
||||
blue: View,
|
||||
red: View,
|
||||
green: View,
|
||||
window: Window
|
||||
green: View
|
||||
}
|
||||
|
||||
impl WindowDelegate for AppWindow {
|
||||
|
@ -35,13 +34,18 @@ impl WindowDelegate for AppWindow {
|
|||
window.set_title("AutoLayout Example");
|
||||
window.set_minimum_content_size(300., 300.);
|
||||
|
||||
self.blue.set_background_color(rgb(105, 162, 176));
|
||||
let dynamic = Color::dynamic(|style| match (style.theme, style.contrast) {
|
||||
(Theme::Dark, _) => Color::SystemGreen,
|
||||
_ => Color::SystemRed
|
||||
});
|
||||
|
||||
self.blue.set_background_color(Color::SystemBlue);
|
||||
self.content.add_subview(&self.blue);
|
||||
|
||||
self.red.set_background_color(rgb(224, 82, 99));
|
||||
self.red.set_background_color(Color::SystemRed);
|
||||
self.content.add_subview(&self.red);
|
||||
|
||||
self.green.set_background_color(rgb(161, 192, 132));
|
||||
self.green.set_background_color(dynamic);
|
||||
self.content.add_subview(&self.green);
|
||||
|
||||
window.set_content_view(&self.content);
|
||||
|
|
82
examples/calculator/button_row.rs
Normal file
82
examples/calculator/button_row.rs
Normal file
|
@ -0,0 +1,82 @@
|
|||
use cacao::layout::{LayoutConstraint, Layout};
|
||||
use cacao::button::Button;
|
||||
use cacao::color::Color;
|
||||
use cacao::view::View;
|
||||
|
||||
use crate::calculator::Msg;
|
||||
use crate::content_view::{button, BUTTON_WIDTH, BUTTON_HEIGHT};
|
||||
|
||||
pub struct ButtonRow {
|
||||
pub view: View,
|
||||
pub buttons: Vec<Button>
|
||||
}
|
||||
|
||||
impl ButtonRow {
|
||||
pub fn new(x: [Msg; 4], color: Color, action_color: Color) -> Self {
|
||||
let view = View::new();
|
||||
|
||||
let buttons: Vec<Button> = x.iter().map(|y| {
|
||||
let button = button(match y {
|
||||
Msg::Clear => "C",
|
||||
Msg::Add => "+",
|
||||
Msg::Subtract => "-",
|
||||
Msg::Multiply => "X",
|
||||
Msg::Divide => "/",
|
||||
Msg::Invert => "+/-",
|
||||
Msg::Mod => "%",
|
||||
Msg::Push(i) if *i == 1 => "1",
|
||||
Msg::Push(i) if *i == 2 => "2",
|
||||
Msg::Push(i) if *i == 3 => "3",
|
||||
Msg::Push(i) if *i == 4 => "4",
|
||||
Msg::Push(i) if *i == 5 => "5",
|
||||
Msg::Push(i) if *i == 6 => "6",
|
||||
Msg::Push(i) if *i == 7 => "7",
|
||||
Msg::Push(i) if *i == 8 => "8",
|
||||
Msg::Push(i) if *i == 9 => "9",
|
||||
_ => "W"
|
||||
|
||||
}, y.clone());
|
||||
|
||||
view.add_subview(&button);
|
||||
button
|
||||
}).collect();
|
||||
|
||||
buttons[0].set_background_color(color.clone());
|
||||
buttons[1].set_background_color(color.clone());
|
||||
buttons[2].set_background_color(color);
|
||||
buttons[3].set_background_color(action_color);
|
||||
|
||||
let width = &buttons[0].width;
|
||||
|
||||
LayoutConstraint::activate(&[
|
||||
buttons[0].top.constraint_equal_to(&view.top),
|
||||
buttons[0].leading.constraint_equal_to(&view.leading),
|
||||
buttons[0].bottom.constraint_equal_to(&view.bottom),
|
||||
width.constraint_equal_to_constant(BUTTON_WIDTH),
|
||||
|
||||
buttons[1].top.constraint_equal_to(&view.top),
|
||||
buttons[1].leading.constraint_equal_to(&buttons[0].trailing).offset(1.),
|
||||
buttons[1].bottom.constraint_equal_to(&view.bottom),
|
||||
buttons[1].width.constraint_equal_to(&width),
|
||||
|
||||
buttons[2].top.constraint_equal_to(&view.top),
|
||||
buttons[2].leading.constraint_equal_to(&buttons[1].trailing).offset(1.),
|
||||
buttons[2].bottom.constraint_equal_to(&view.bottom),
|
||||
buttons[2].width.constraint_equal_to(&width),
|
||||
|
||||
buttons[3].top.constraint_equal_to(&view.top),
|
||||
buttons[3].leading.constraint_equal_to(&buttons[2].trailing).offset(1.),
|
||||
buttons[3].trailing.constraint_equal_to(&view.trailing),
|
||||
buttons[3].bottom.constraint_equal_to(&view.bottom),
|
||||
buttons[3].width.constraint_equal_to(&width),
|
||||
|
||||
view.height.constraint_equal_to_constant(BUTTON_HEIGHT)
|
||||
]);
|
||||
|
||||
Self {
|
||||
view,
|
||||
buttons
|
||||
}
|
||||
}
|
||||
}
|
||||
|
105
examples/calculator/calculator.rs
Normal file
105
examples/calculator/calculator.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use cacao::lazy_static::lazy_static;
|
||||
use cacao::macos::App;
|
||||
use cacao::notification_center::Dispatcher;
|
||||
|
||||
use crate::CalculatorApp;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Msg {
|
||||
Push(i32),
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Decimal,
|
||||
Clear,
|
||||
Mod,
|
||||
Invert,
|
||||
Equals
|
||||
}
|
||||
|
||||
/// Asynchronously calls back through to the top of the application
|
||||
/// on the main thread.
|
||||
pub fn dispatch(msg: Msg) {
|
||||
println!("Dispatching UI message: {:?}", msg);
|
||||
//App::<CalculatorApp, Msg>::dispatch_main(msg)
|
||||
CALCULATOR.run(msg)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CALCULATOR: Calculator = Calculator::new();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Calculator(Arc<RwLock<Vec<String>>>);
|
||||
|
||||
impl Calculator {
|
||||
pub fn new() -> Self {
|
||||
Calculator(Arc::new(RwLock::new(Vec::new())))
|
||||
}
|
||||
|
||||
pub fn run(&self, message: Msg) {
|
||||
let mut expression = self.0.write().unwrap();
|
||||
|
||||
match message {
|
||||
Msg::Push(i) => {
|
||||
// Realistically you might want to check decimal length here or something.
|
||||
// We're not bothering for this example.
|
||||
(*expression).push(i.to_string());
|
||||
let display = (*expression).join("").split(" ").last().unwrap_or("0").to_string();
|
||||
App::<CalculatorApp, String>::dispatch_main(display);
|
||||
},
|
||||
|
||||
Msg::Decimal => {
|
||||
let display = (*expression).join("").split(" ").last().unwrap_or("0").to_string();
|
||||
if !display.contains(".") {
|
||||
(*expression).push(".".to_string());
|
||||
App::<CalculatorApp, String>::dispatch_main(display + ".");
|
||||
}
|
||||
},
|
||||
|
||||
Msg::Add => {
|
||||
if let Some(last_entry) = (*expression).last() {
|
||||
if !last_entry.ends_with(" ") {
|
||||
(*expression).push(" + ".to_string());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Msg::Subtract => {
|
||||
(*expression).push(" - ".to_string());
|
||||
},
|
||||
|
||||
Msg::Multiply => {
|
||||
(*expression).push(" * ".to_string());
|
||||
},
|
||||
|
||||
Msg::Divide => {
|
||||
(*expression).push(" / ".to_string());
|
||||
},
|
||||
|
||||
Msg::Clear => {
|
||||
(*expression) = Vec::new();
|
||||
App::<CalculatorApp, String>::dispatch_main("0".to_string())
|
||||
},
|
||||
|
||||
Msg::Equals => {
|
||||
let mut expr = (*expression).join("");
|
||||
if expr.ends_with(" ") {
|
||||
expr.truncate(expr.len() - 3);
|
||||
}
|
||||
|
||||
println!("Expr: {}", expr);
|
||||
|
||||
match eval::eval(&expr) {
|
||||
Ok(val) => { App::<CalculatorApp, String>::dispatch_main(val.to_string()); },
|
||||
Err(e) => { eprintln!("Error parsing expression: {:?}", e); }
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
151
examples/calculator/content_view.rs
Normal file
151
examples/calculator/content_view.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
use cacao::text::{Font, Label, TextAlign};
|
||||
use cacao::layout::{LayoutConstraint, Layout};
|
||||
use cacao::button::{Button, BezelStyle};
|
||||
use cacao::color::Color;
|
||||
use cacao::macos::FocusRingType;
|
||||
use cacao::view::{View, ViewDelegate};
|
||||
|
||||
use crate::button_row::ButtonRow;
|
||||
use crate::calculator::{dispatch, Msg};
|
||||
|
||||
pub const BUTTON_WIDTH: f64 = 57.;
|
||||
pub const BUTTON_HEIGHT: f64 = 47.;
|
||||
|
||||
pub fn button(text: &str, msg: Msg) -> Button {
|
||||
let mut button = Button::new(text);
|
||||
button.set_bordered(false);
|
||||
button.set_bezel_style(BezelStyle::SmallSquare);
|
||||
button.set_focus_ring_type(FocusRingType::None);
|
||||
button.set_action(move || dispatch(msg.clone()));
|
||||
button.set_key_equivalent(&text.to_lowercase());
|
||||
|
||||
let font = Font::system(22.);
|
||||
button.set_font(&font);
|
||||
button.set_text_color(Color::SystemWhite);
|
||||
|
||||
button
|
||||
}
|
||||
|
||||
pub struct CalculatorView {
|
||||
pub results_wrapper: View,
|
||||
pub label: Label,
|
||||
pub row0: ButtonRow,
|
||||
pub row1: ButtonRow,
|
||||
pub row2: ButtonRow,
|
||||
pub row3: ButtonRow,
|
||||
pub dot: Button,
|
||||
pub zero: Button,
|
||||
pub equals: Button
|
||||
}
|
||||
|
||||
impl CalculatorView {
|
||||
pub fn new() -> Self {
|
||||
let results_wrapper = View::new();
|
||||
|
||||
let label = Label::new();
|
||||
let font = Font::system(40.);
|
||||
label.set_font(&font);
|
||||
label.set_text("0");
|
||||
label.set_text_color(Color::rgb(255, 255, 255));
|
||||
label.set_text_alignment(TextAlign::Right);
|
||||
|
||||
Self {
|
||||
results_wrapper,
|
||||
label,
|
||||
|
||||
row0: ButtonRow::new([
|
||||
Msg::Clear, Msg::Invert, Msg::Mod, Msg::Divide
|
||||
], Color::rgb(69, 69, 69), Color::rgb(255, 148, 10)),
|
||||
|
||||
row1: ButtonRow::new([
|
||||
Msg::Push(7), Msg::Push(8), Msg::Push(9), Msg::Multiply
|
||||
], Color::rgb(100, 100, 100), Color::rgb(255, 148, 10)),
|
||||
|
||||
row2: ButtonRow::new([
|
||||
Msg::Push(4), Msg::Push(5), Msg::Push(6), Msg::Subtract
|
||||
], Color::rgb(100, 100, 100), Color::rgb(255, 148, 10)),
|
||||
|
||||
row3: ButtonRow::new([
|
||||
Msg::Push(1), Msg::Push(2), Msg::Push(3), Msg::Add
|
||||
], Color::rgb(100, 100, 100), Color::rgb(255, 148, 10)),
|
||||
|
||||
zero: button("0", Msg::Push(0)),
|
||||
dot: button(".", Msg::Decimal),
|
||||
equals: button("=", Msg::Equals)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_update(&self, message: String) {
|
||||
self.label.set_text(&message);
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewDelegate for CalculatorView {
|
||||
const NAME: &'static str = "CalculatorView";
|
||||
|
||||
fn did_load(&mut self, view: View) {
|
||||
view.set_background_color(Color::rgb(49, 49, 49));
|
||||
self.zero.set_background_color(Color::rgb(100, 100, 100));
|
||||
self.dot.set_background_color(Color::rgb(100, 100, 100));
|
||||
self.equals.set_background_color(Color::rgb(255, 148, 10));
|
||||
|
||||
self.zero.set_key_equivalent("0");
|
||||
|
||||
view.add_subview(&self.row0.view);
|
||||
view.add_subview(&self.row1.view);
|
||||
view.add_subview(&self.row2.view);
|
||||
view.add_subview(&self.row3.view);
|
||||
|
||||
for button in &[&self.zero, &self.dot, &self.equals] {
|
||||
view.add_subview(button);
|
||||
}
|
||||
|
||||
self.results_wrapper.add_subview(&self.label);
|
||||
view.add_subview(&self.results_wrapper);
|
||||
|
||||
LayoutConstraint::activate(&[
|
||||
self.results_wrapper.top.constraint_equal_to(&view.top),
|
||||
self.results_wrapper.leading.constraint_equal_to(&view.leading),
|
||||
self.results_wrapper.trailing.constraint_equal_to(&view.trailing),
|
||||
self.results_wrapper.height.constraint_equal_to_constant(52.),
|
||||
|
||||
self.label.leading.constraint_equal_to(&self.results_wrapper.leading).offset(22.),
|
||||
self.label.trailing.constraint_equal_to(&self.results_wrapper.trailing).offset(-16.),
|
||||
self.label.bottom.constraint_equal_to(&self.results_wrapper.bottom).offset(-4.),
|
||||
|
||||
// Buttons laid out from top-left
|
||||
self.row0.view.top.constraint_equal_to(&self.results_wrapper.bottom).offset(1.),
|
||||
self.row0.view.leading.constraint_equal_to(&view.leading),
|
||||
self.row0.view.trailing.constraint_equal_to(&view.trailing),
|
||||
|
||||
self.row1.view.top.constraint_equal_to(&self.row0.view.bottom).offset(1.),
|
||||
self.row1.view.leading.constraint_equal_to(&view.leading),
|
||||
self.row1.view.trailing.constraint_equal_to(&view.trailing),
|
||||
|
||||
self.row2.view.top.constraint_equal_to(&self.row1.view.bottom).offset(1.),
|
||||
self.row2.view.leading.constraint_equal_to(&view.leading),
|
||||
self.row2.view.trailing.constraint_equal_to(&view.trailing),
|
||||
|
||||
self.row3.view.top.constraint_equal_to(&self.row2.view.bottom).offset(1.),
|
||||
self.row3.view.leading.constraint_equal_to(&view.leading),
|
||||
self.row3.view.trailing.constraint_equal_to(&view.trailing),
|
||||
|
||||
self.zero.top.constraint_equal_to(&self.row3.view.bottom).offset(1.),
|
||||
self.zero.leading.constraint_equal_to(&view.leading),
|
||||
self.zero.bottom.constraint_equal_to(&view.bottom),
|
||||
|
||||
self.dot.top.constraint_equal_to(&self.row3.view.bottom).offset(1.),
|
||||
self.dot.leading.constraint_equal_to(&self.zero.trailing).offset(1.),
|
||||
self.dot.bottom.constraint_equal_to(&view.bottom),
|
||||
self.dot.width.constraint_equal_to_constant(BUTTON_WIDTH),
|
||||
self.dot.height.constraint_equal_to_constant(BUTTON_HEIGHT),
|
||||
|
||||
self.equals.top.constraint_equal_to(&self.row3.view.bottom).offset(1.),
|
||||
self.equals.leading.constraint_equal_to(&self.dot.trailing).offset(1.),
|
||||
self.equals.trailing.constraint_equal_to(&view.trailing),
|
||||
self.equals.bottom.constraint_equal_to(&view.bottom),
|
||||
self.equals.width.constraint_equal_to_constant(BUTTON_WIDTH),
|
||||
self.equals.height.constraint_equal_to_constant(BUTTON_HEIGHT)
|
||||
])
|
||||
}
|
||||
}
|
109
examples/calculator/main.rs
Normal file
109
examples/calculator/main.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
//! This example implements a basic macOS Calculator clone. It showcases:
|
||||
//!
|
||||
//! - A single-window app
|
||||
//! - Button handling
|
||||
//! - Autolayout
|
||||
//! - Message dispatching
|
||||
//! - Global key/event handling
|
||||
//!
|
||||
//! It does not attempt to be a good calculator, and does not implement the
|
||||
//! extended Calculator view.
|
||||
|
||||
use std::sync::RwLock;
|
||||
|
||||
use cacao::macos::{App, AppDelegate};
|
||||
use cacao::macos::window::{Window, WindowConfig, TitleVisibility};
|
||||
use cacao::macos::{Event, EventMask, EventMonitor};
|
||||
use cacao::color::Color;
|
||||
use cacao::notification_center::Dispatcher;
|
||||
use cacao::view::{View, ViewDelegate};
|
||||
|
||||
mod button_row;
|
||||
mod calculator;
|
||||
use calculator::{dispatch, Msg};
|
||||
|
||||
mod content_view;
|
||||
use content_view::CalculatorView;
|
||||
|
||||
struct CalculatorApp {
|
||||
window: Window,
|
||||
content: View<CalculatorView>,
|
||||
key_monitor: RwLock<Option<EventMonitor>>
|
||||
}
|
||||
|
||||
impl AppDelegate for CalculatorApp {
|
||||
fn did_finish_launching(&self) {
|
||||
App::activate();
|
||||
|
||||
// Event Monitors need to be started after the App has been activated.
|
||||
// We use an RwLock here, but it's possible this entire method can be
|
||||
// &mut self and you wouldn't need these kinds of shenanigans.
|
||||
//self.start_monitoring();
|
||||
|
||||
self.window.set_title("Calculator");
|
||||
self.window.set_background_color(Color::rgb(49,49,49));
|
||||
self.window.set_title_visibility(TitleVisibility::Hidden);
|
||||
self.window.set_titlebar_appears_transparent(true);
|
||||
self.window.set_movable_by_background(true);
|
||||
self.window.set_autosave_name("CacaoCalculatorExampleWindow");
|
||||
self.window.set_content_view(&self.content);
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatcher for CalculatorApp {
|
||||
type Message = String;
|
||||
|
||||
fn on_ui_message(&self, message: Self::Message) {
|
||||
if let Some(delegate) = &self.content.delegate {
|
||||
delegate.render_update(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalculatorApp {
|
||||
/// Monitor for key presses, and dispatch if they match an action
|
||||
/// we're after.
|
||||
pub fn start_monitoring(&self) {
|
||||
let mut lock = self.key_monitor.write().unwrap();
|
||||
*lock = Some(Event::local_monitor(EventMask::KeyDown, |evt| {
|
||||
let characters = evt.characters();
|
||||
println!("{}", characters);
|
||||
|
||||
match characters.as_ref() {
|
||||
"0" => dispatch(Msg::Push(0)),
|
||||
"1" => dispatch(Msg::Push(1)),
|
||||
"2" => dispatch(Msg::Push(2)),
|
||||
"3" => dispatch(Msg::Push(3)),
|
||||
"4" => dispatch(Msg::Push(4)),
|
||||
"5" => dispatch(Msg::Push(5)),
|
||||
"6" => dispatch(Msg::Push(6)),
|
||||
"7" => dispatch(Msg::Push(7)),
|
||||
"8" => dispatch(Msg::Push(8)),
|
||||
"9" => dispatch(Msg::Push(9)),
|
||||
"+" => dispatch(Msg::Add),
|
||||
"-" => dispatch(Msg::Subtract),
|
||||
"*" => dispatch(Msg::Multiply),
|
||||
"/" => dispatch(Msg::Divide),
|
||||
"=" => dispatch(Msg::Equals),
|
||||
"%" => dispatch(Msg::Mod),
|
||||
"c" => dispatch(Msg::Clear),
|
||||
"." => dispatch(Msg::Decimal),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
None
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut config = WindowConfig::default();
|
||||
config.set_initial_dimensions(100., 100., 240., 300.);
|
||||
|
||||
App::new("com.example.calculator", CalculatorApp {
|
||||
window: Window::new(config),
|
||||
content: View::with(CalculatorView::new()),
|
||||
key_monitor: RwLock::new(None)
|
||||
}).run();
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use cacao::layout::{Layout, LayoutConstraint};
|
||||
use cacao::text::{Font, Label, LineBreakMode};
|
||||
use cacao::view::{View, ViewDelegate};
|
||||
use cacao::color::rgb;
|
||||
use cacao::color::Color;
|
||||
|
||||
use crate::storage::{Todo, TodoStatus};
|
||||
|
||||
|
@ -20,12 +20,12 @@ impl TodoViewRow {
|
|||
|
||||
match todo.status {
|
||||
TodoStatus::Incomplete => {
|
||||
self.status.set_color(rgb(219, 66, 66));
|
||||
self.status.set_text_color(Color::SystemRed);
|
||||
self.status.set_text("Incomplete");
|
||||
},
|
||||
|
||||
TodoStatus::Complete => {
|
||||
self.status.set_color(rgb(11, 121, 254));
|
||||
self.status.set_text_color(Color::SystemBlue);
|
||||
self.status.set_text("Complete");
|
||||
}
|
||||
}
|
||||
|
|
176
src/button.rs
176
src/button.rs
|
@ -9,11 +9,20 @@ use objc::declare::ClassDecl;
|
|||
use objc::runtime::{Class, Object, Sel};
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
use crate::foundation::{id, nil, BOOL, YES, NO, NSString};
|
||||
use crate::color::Color;
|
||||
use crate::foundation::{id, nil, BOOL, YES, NO, NSString, NSUInteger};
|
||||
use crate::invoker::TargetActionHandler;
|
||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||
use crate::text::Font;
|
||||
use crate::utils::load;
|
||||
|
||||
#[cfg(feature = "macos")]
|
||||
use crate::macos::FocusRingType;
|
||||
|
||||
extern "C" {
|
||||
static NSForegroundColorAttributeName: id;
|
||||
}
|
||||
|
||||
/// A wrapper for `NSButton`. Holds (retains) pointers for the Objective-C runtime
|
||||
/// where our `NSButton` lives.
|
||||
#[derive(Debug)]
|
||||
|
@ -54,6 +63,7 @@ impl Button {
|
|||
|
||||
let view: id = unsafe {
|
||||
let button: id = msg_send![register_class(), buttonWithTitle:title target:nil action:nil];
|
||||
let _: () = msg_send![button, setWantsLayer:YES];
|
||||
let _: () = msg_send![button, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
button
|
||||
};
|
||||
|
@ -73,9 +83,12 @@ impl Button {
|
|||
}
|
||||
|
||||
/// Sets the bezel style for this button.
|
||||
pub fn set_bezel_style(&self, bezel_style: i32) {
|
||||
#[cfg(feature = "macos")]
|
||||
pub fn set_bezel_style(&self, bezel_style: BezelStyle) {
|
||||
let style: NSUInteger = bezel_style.into();
|
||||
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, setBezelStyle:bezel_style];
|
||||
let _: () = msg_send![&*self.objc, setBezelStyle:style];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,6 +98,85 @@ impl Button {
|
|||
let handler = TargetActionHandler::new(&*self.objc, action);
|
||||
self.handler = Some(handler);
|
||||
}
|
||||
|
||||
/// Call this to set the background color for the backing layer.
|
||||
pub fn set_background_color(&self, color: Color) {
|
||||
let bg = color.into_platform_specific_color();
|
||||
|
||||
#[cfg(feature = "macos")]
|
||||
unsafe {
|
||||
let cell: id = msg_send![&*self.objc, cell];
|
||||
let _: () = msg_send![cell, setBackgroundColor:bg];
|
||||
/*let cg: id = msg_send![bg, CGColor];
|
||||
let layer: id = msg_send![&*self.objc, layer];
|
||||
let _: () = msg_send![layer, setBackgroundColor:cg];
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_key_equivalent(&self, key: &str) {
|
||||
let key = NSString::new(key).into_inner();
|
||||
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, setKeyEquivalent:key];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_text_color(&self, color: Color) {
|
||||
let bg = color.into_platform_specific_color();
|
||||
|
||||
// @TODO: Clean this up, and look at just using `CFMutableAttributedString` instead
|
||||
// to avoid ObjC overhead.
|
||||
unsafe {
|
||||
let alloc: id = msg_send![class!(NSMutableAttributedString), alloc];
|
||||
let s: id = msg_send![&*self.objc, attributedTitle];
|
||||
let attributed_string: id = msg_send![alloc, initWithAttributedString:s];
|
||||
let len: isize = msg_send![s, length];
|
||||
let range = core_foundation::base::CFRange::init(0, len);
|
||||
|
||||
let _: () = msg_send![attributed_string, addAttribute:NSForegroundColorAttributeName value:bg range:range];
|
||||
let _: () = msg_send![&*self.objc, setAttributedTitle:attributed_string];
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO: Figure out how to handle oddities like this.
|
||||
/// For buttons on macOS, one might need to disable the border. This does that.
|
||||
#[cfg(feature = "macos")]
|
||||
pub fn set_bordered(&self, is_bordered: bool) {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, setBordered:match is_bordered {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the font for this button.
|
||||
pub fn set_font(&self, font: &Font) {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, setFont:&*font.objc];
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets how the control should draw a focus ring when a user is focused on it.
|
||||
#[cfg(feature = "macos")]
|
||||
pub fn set_focus_ring_type(&self, focus_ring_type: FocusRingType) {
|
||||
let ring_type: NSUInteger = focus_ring_type.into();
|
||||
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, setFocusRingType:ring_type];
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggles the highlighted status of the button.
|
||||
pub fn set_highlighted(&self, highlight: bool) {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, highlight:match highlight {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for Button {
|
||||
|
@ -100,6 +192,19 @@ impl Layout for Button {
|
|||
}
|
||||
}
|
||||
|
||||
impl Layout for &Button {
|
||||
fn get_backing_node(&self) -> ShareId<Object> {
|
||||
self.objc.clone()
|
||||
}
|
||||
|
||||
fn add_subview<V: Layout>(&self, _view: &V) {
|
||||
panic!(r#"
|
||||
Tried to add a subview to a Button. This is not allowed in Cacao. If you think this should be supported,
|
||||
open a discussion on the GitHub repo.
|
||||
"#);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Drop for Button {
|
||||
// Just to be sure, let's... nil these out. They should be weak references,
|
||||
|
@ -126,3 +231,68 @@ fn register_class() -> *const Class {
|
|||
|
||||
unsafe { VIEW_CLASS }
|
||||
}
|
||||
|
||||
/// Represents a bezel style for a button. This is a macOS-specific control, and has no effect
|
||||
/// under iOS or tvOS.
|
||||
#[cfg(feature = "macos")]
|
||||
#[derive(Debug)]
|
||||
pub enum BezelStyle {
|
||||
Circular,
|
||||
Disclosure,
|
||||
HelpButton,
|
||||
Inline,
|
||||
Recessed,
|
||||
RegularSquare,
|
||||
RoundRect,
|
||||
Rounded,
|
||||
RoundedDisclosure,
|
||||
ShadowlessSquare,
|
||||
SmallSquare,
|
||||
TexturedRounded,
|
||||
TexturedSquare,
|
||||
Unknown(NSUInteger)
|
||||
}
|
||||
|
||||
#[cfg(feature = "macos")]
|
||||
impl From<BezelStyle> for NSUInteger {
|
||||
fn from(style: BezelStyle) -> Self {
|
||||
match style {
|
||||
BezelStyle::Circular => 7,
|
||||
BezelStyle::Disclosure => 5,
|
||||
BezelStyle::HelpButton => 9,
|
||||
BezelStyle::Inline => 15,
|
||||
BezelStyle::Recessed => 13,
|
||||
BezelStyle::RegularSquare => 2,
|
||||
BezelStyle::RoundRect => 12,
|
||||
BezelStyle::Rounded => 1,
|
||||
BezelStyle::RoundedDisclosure => 14,
|
||||
BezelStyle::ShadowlessSquare => 6,
|
||||
BezelStyle::SmallSquare => 10,
|
||||
BezelStyle::TexturedRounded => 11,
|
||||
BezelStyle::TexturedSquare => 8,
|
||||
BezelStyle::Unknown(i) => i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "macos")]
|
||||
impl From<NSUInteger> for BezelStyle {
|
||||
fn from(i: NSUInteger) -> Self {
|
||||
match i {
|
||||
7 => Self::Circular,
|
||||
5 => Self::Disclosure,
|
||||
9 => Self::HelpButton,
|
||||
15 => Self::Inline,
|
||||
13 => Self::Recessed,
|
||||
2 => Self::RegularSquare,
|
||||
12 => Self::RoundRect,
|
||||
1 => Self::Rounded,
|
||||
14 => Self::RoundedDisclosure,
|
||||
6 => Self::ShadowlessSquare,
|
||||
10 => Self::SmallSquare,
|
||||
11 => Self::TexturedRounded,
|
||||
8 => Self::TexturedSquare,
|
||||
i => Self::Unknown(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
138
src/color.rs
138
src/color.rs
|
@ -1,138 +0,0 @@
|
|||
//! Implements `Color`. Heavily based on the `Color` module in Servo's CSS parser, but tweaked
|
||||
//! for (what I believe) is a friendlier API.
|
||||
|
||||
use core_graphics::base::CGFloat;
|
||||
use core_graphics::color::CGColor;
|
||||
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
use crate::foundation::id;
|
||||
|
||||
/// A color with red, green, blue, and alpha components, in a byte each.
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub struct Color {
|
||||
/// The red component.
|
||||
pub red: u8,
|
||||
/// The green component.
|
||||
pub green: u8,
|
||||
/// The blue component.
|
||||
pub blue: u8,
|
||||
/// The alpha component.
|
||||
pub alpha: u8,
|
||||
}
|
||||
|
||||
impl Default for Color {
|
||||
fn default() -> Color {
|
||||
Color { red: 0, green: 0, blue: 0, alpha: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Constructs a new Color value from float components. It expects the red,
|
||||
/// green, blue and alpha channels in that order, and all values will be
|
||||
/// clamped to the 0.0 ... 1.0 range.
|
||||
#[inline]
|
||||
pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
|
||||
Self::new(
|
||||
clamp_unit_f32(red),
|
||||
clamp_unit_f32(green),
|
||||
clamp_unit_f32(blue),
|
||||
clamp_unit_f32(alpha),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a transparent color.
|
||||
#[inline]
|
||||
pub fn transparent() -> Self {
|
||||
Self::new(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
/// Same thing, but with `u8` values instead of floats in the 0 to 1 range.
|
||||
#[inline]
|
||||
pub fn new(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
|
||||
Color {
|
||||
red: red,
|
||||
green: green,
|
||||
blue: blue,
|
||||
alpha: alpha,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps to NS/UIColor.
|
||||
pub fn into_platform_specific_color(&self) -> id {
|
||||
let red = self.red as CGFloat / 255.0;
|
||||
let green = self.green as CGFloat / 255.0;
|
||||
let blue = self.blue as CGFloat / 255.0;
|
||||
let alpha = self.alpha as CGFloat / 255.0;
|
||||
|
||||
unsafe {
|
||||
msg_send![class!(NSColor), colorWithRed:red green:green blue:blue alpha:alpha]
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps to CGColor, used across platforms.
|
||||
pub fn cg_color(&self) -> CGColor {
|
||||
let red = self.red as CGFloat / 255.0;
|
||||
let green = self.green as CGFloat / 255.0;
|
||||
let blue = self.blue as CGFloat / 255.0;
|
||||
let alpha = self.alpha as CGFloat / 255.0;
|
||||
|
||||
CGColor::rgb(red, green, blue, alpha)
|
||||
}
|
||||
|
||||
/// Returns the red channel in a floating point number form, from 0 to 1.
|
||||
#[inline]
|
||||
pub fn red_f32(&self) -> f32 {
|
||||
self.red as f32 / 255.0
|
||||
}
|
||||
|
||||
/// Returns the green channel in a floating point number form, from 0 to 1.
|
||||
#[inline]
|
||||
pub fn green_f32(&self) -> f32 {
|
||||
self.green as f32 / 255.0
|
||||
}
|
||||
|
||||
/// Returns the blue channel in a floating point number form, from 0 to 1.
|
||||
#[inline]
|
||||
pub fn blue_f32(&self) -> f32 {
|
||||
self.blue as f32 / 255.0
|
||||
}
|
||||
|
||||
/// Returns the alpha channel in a floating point number form, from 0 to 1.
|
||||
#[inline]
|
||||
pub fn alpha_f32(&self) -> f32 {
|
||||
self.alpha as f32 / 255.0
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rgb(red: u8, green: u8, blue: u8) -> Color {
|
||||
rgba(red, green, blue, 255)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
|
||||
Color::new(red, green, blue, alpha)
|
||||
}
|
||||
|
||||
fn clamp_unit_f32(val: f32) -> u8 {
|
||||
// Whilst scaling by 256 and flooring would provide
|
||||
// an equal distribution of integers to percentage inputs,
|
||||
// this is not what Gecko does so we instead multiply by 255
|
||||
// and round (adding 0.5 and flooring is equivalent to rounding)
|
||||
//
|
||||
// Chrome does something similar for the alpha value, but not
|
||||
// the rgb values.
|
||||
//
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1340484
|
||||
//
|
||||
// Clamping to 256 and rounding after would let 1.0 map to 256, and
|
||||
// `256.0_f32 as u8` is undefined behavior:
|
||||
//
|
||||
// https://github.com/rust-lang/rust/issues/10184
|
||||
clamp_floor_256_f32(val * 255.)
|
||||
}
|
||||
|
||||
fn clamp_floor_256_f32(val: f32) -> u8 {
|
||||
val.round().max(0.).min(255.) as u8
|
||||
}
|
298
src/color/macos_dynamic_color.rs
Normal file
298
src/color/macos_dynamic_color.rs
Normal file
|
@ -0,0 +1,298 @@
|
|||
//! This module provides a custom NSColor subclass for macOS that mimics the dynamic
|
||||
//! UIColor provider found on iOS. Notably, this works with older versions of macOS as
|
||||
//! well; it runs the block on creation and caches the created color instances to avoid
|
||||
//! repeated allocations - this might not be a big thing to worry about as NSColor
|
||||
//! changed slightly behind the scenes in 10.15+, so this could be changed down the
|
||||
//! road.
|
||||
//!
|
||||
//! On versions where dark mode is not supported (e.g, pre-Mojave) this will return the
|
||||
//! provided light color. Note that while 10.15 did introduce an `NSColor` initializer
|
||||
//! that enables this functionality, we want to be able to provide this with some level of
|
||||
//! backwards compatibility for Mojave, as that's still a supported OS.
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
use core_graphics::base::CGFloat;
|
||||
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{Class, Object, Sel, BOOL};
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
use crate::foundation::{id, nil, YES, NO, NSInteger, NSUInteger, NSString, NSArray};
|
||||
use crate::utils::os;
|
||||
|
||||
pub(crate) const AQUA_LIGHT_COLOR_NORMAL_CONTRAST: &'static str = "AQUA_LIGHT_COLOR_NORMAL_CONTRAST";
|
||||
pub(crate) const AQUA_LIGHT_COLOR_HIGH_CONTRAST: &'static str = "AQUA_LIGHT_COLOR_HIGH_CONTRAST";
|
||||
pub(crate) const AQUA_DARK_COLOR_NORMAL_CONTRAST: &'static str = "AQUA_DARK_COLOR_NORMAL_CONTRAST";
|
||||
pub(crate) const AQUA_DARK_COLOR_HIGH_CONTRAST: &'static str = "AQUA_DARK_COLOR_HIGH_CONTRAST";
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
extern "C" {
|
||||
static NSAppearanceNameAqua: id;
|
||||
static NSAppearanceNameAccessibilityHighContrastAqua: id;
|
||||
static NSAppearanceNameDarkAqua: id;
|
||||
static NSAppearanceNameAccessibilityHighContrastDarkAqua: id;
|
||||
}
|
||||
|
||||
/// This function accepts an `Object` (our `CacaoDynamicColor` instance) and queries the system
|
||||
/// to determine which color should be used. Note that this currently does not support high
|
||||
/// contrast checking on systems prior to 10.14: it's not that it couldn't be supported, but the
|
||||
/// ongoing question of how far back to support makes this not worth bothering with right now.
|
||||
///
|
||||
/// Pull requests to implement that check would be welcome.
|
||||
fn get_effective_color(this: &Object) -> id {
|
||||
if os::is_minimum_semversion(10, 14, 0) {
|
||||
unsafe {
|
||||
let mut appearance: id = msg_send![class!(NSAppearance), currentAppearance];
|
||||
if appearance == nil {
|
||||
appearance = msg_send![class!(NSApp), effectiveAppearance];
|
||||
}
|
||||
|
||||
let names = NSArray::new(&[
|
||||
NSAppearanceNameAqua,
|
||||
NSAppearanceNameAccessibilityHighContrastAqua,
|
||||
NSAppearanceNameDarkAqua,
|
||||
NSAppearanceNameAccessibilityHighContrastDarkAqua
|
||||
]);
|
||||
|
||||
let style: id = msg_send![appearance, bestMatchFromAppearancesWithNames:names.into_inner()];
|
||||
|
||||
if style == NSAppearanceNameDarkAqua {
|
||||
return *this.get_ivar(AQUA_DARK_COLOR_NORMAL_CONTRAST);
|
||||
}
|
||||
|
||||
if style == NSAppearanceNameAccessibilityHighContrastAqua {
|
||||
return *this.get_ivar(AQUA_LIGHT_COLOR_HIGH_CONTRAST);
|
||||
}
|
||||
|
||||
if style == NSAppearanceNameAccessibilityHighContrastDarkAqua {
|
||||
return *this.get_ivar(AQUA_DARK_COLOR_HIGH_CONTRAST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
return *this.get_ivar(AQUA_LIGHT_COLOR_NORMAL_CONTRAST);
|
||||
}
|
||||
}
|
||||
|
||||
extern fn color_space(this: &Object, _: Sel) -> id {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, colorSpace] }
|
||||
}
|
||||
|
||||
extern fn color_using_color_space(this: &Object, _: Sel, color_space: id) -> id {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, colorUsingColorSpace:color_space] }
|
||||
}
|
||||
|
||||
extern fn color_space_name(this: &Object, _: Sel) -> id {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, colorSpaceName] }
|
||||
}
|
||||
|
||||
extern fn color_using_color_space_name(this: &Object, _: Sel, color_space_name: id) -> id {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, colorUsingColorSpaceName:color_space_name] }
|
||||
}
|
||||
|
||||
extern fn number_of_components(this: &Object, _: Sel) -> NSInteger {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, numberOfComponents] }
|
||||
}
|
||||
|
||||
// @TODO: Confirm this.
|
||||
extern fn get_components(this: &Object, _: Sel, components: CGFloat) {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { let _: () = msg_send![color, getComponents:components]; }
|
||||
}
|
||||
|
||||
// @TODO: Confirm this.
|
||||
extern fn get_rgba(this: &Object, _: Sel, red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { let _: () = msg_send![color, getRed:red green:green blue:blue alpha:alpha]; }
|
||||
}
|
||||
|
||||
extern fn red_component(this: &Object, _: Sel) -> CGFloat {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, redComponent] }
|
||||
}
|
||||
|
||||
extern fn green_component(this: &Object, _: Sel) -> CGFloat {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, greenComponent] }
|
||||
}
|
||||
|
||||
extern fn blue_component(this: &Object, _: Sel) -> CGFloat {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, blueComponent] }
|
||||
}
|
||||
|
||||
extern fn hue_component(this: &Object, _: Sel) -> CGFloat {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, hueComponent] }
|
||||
}
|
||||
|
||||
extern fn saturation_component(this: &Object, _: Sel) -> CGFloat {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, saturationComponent] }
|
||||
}
|
||||
|
||||
extern fn brightness_component(this: &Object, _: Sel) -> CGFloat {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, brightnessComponent] }
|
||||
}
|
||||
|
||||
// @TODO: Confirm this.
|
||||
extern fn get_hsba(this: &Object, _: Sel, hue: CGFloat, sat: CGFloat, brit: CGFloat, alpha: CGFloat) {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { let _: () = msg_send![color, getHue:hue saturation:sat brightness:brit alpha:alpha]; }
|
||||
}
|
||||
|
||||
extern fn white_component(this: &Object, _: Sel) -> CGFloat {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, whiteComponent] }
|
||||
}
|
||||
|
||||
// @TODO: Confirm this.
|
||||
extern fn get_white(this: &Object, _: Sel, white: CGFloat, alpha: CGFloat) {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { let _: () = msg_send![color, getWhite:white alpha:alpha]; }
|
||||
}
|
||||
|
||||
extern fn cyan_component(this: &Object, _: Sel) -> CGFloat {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, cyanComponent] }
|
||||
}
|
||||
|
||||
extern fn magenta_component(this: &Object, _: Sel) -> CGFloat {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, magentaComponent] }
|
||||
}
|
||||
|
||||
extern fn yellow_component(this: &Object, _: Sel) -> CGFloat {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, yellowComponent] }
|
||||
}
|
||||
|
||||
extern fn black_component(this: &Object, _: Sel) -> CGFloat {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, blackComponent] }
|
||||
}
|
||||
|
||||
// @TODO: Confirm this.
|
||||
extern fn get_cmyk(this: &Object, _: Sel, c: CGFloat, m: CGFloat, y: CGFloat, k: CGFloat, a: CGFloat) {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { let _: () = msg_send![color, getCyan:c magenta:m yellow:y black:k alpha:a]; }
|
||||
}
|
||||
|
||||
extern fn alpha_component(this: &Object, _: Sel) -> CGFloat {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, alphaComponent] }
|
||||
}
|
||||
|
||||
extern fn cg_color(this: &Object, _: Sel) -> id {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, CGColor] }
|
||||
}
|
||||
|
||||
extern fn set_stroke(this: &Object, _: Sel) {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { let _: () = msg_send![color, setStroke]; }
|
||||
}
|
||||
|
||||
extern fn set_fill(this: &Object, _: Sel) {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { let _: () = msg_send![color, setFill]; }
|
||||
}
|
||||
|
||||
extern fn call_set(this: &Object, _: Sel) {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { let _: () = msg_send![color, set]; }
|
||||
}
|
||||
|
||||
extern fn highlight_with_level(this: &Object, _: Sel, level: CGFloat) -> id {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, highlightWithLevel:level] }
|
||||
}
|
||||
|
||||
extern fn shadow_with_level(this: &Object, _: Sel, level: CGFloat) -> id {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, shadowWithLevel:level] }
|
||||
}
|
||||
|
||||
extern fn color_with_alpha_component(this: &Object, _: Sel, alpha: CGFloat) -> id {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, colorWithAlphaComponent:alpha] }
|
||||
}
|
||||
|
||||
extern fn blended_color(this: &Object, _: Sel, fraction: CGFloat, with_color: id) -> id {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, blendedColorWithFraction:fraction ofColor:with_color] }
|
||||
}
|
||||
|
||||
extern fn color_with_system_effect(this: &Object, _: Sel, effect: NSInteger) -> id {
|
||||
let color = get_effective_color(this);
|
||||
unsafe { msg_send![color, colorWithSystemEffect:effect] }
|
||||
}
|
||||
|
||||
pub(crate) fn register_class() -> *const Class {
|
||||
static mut VIEW_CLASS: *const Class = 0 as *const Class;
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
INIT.call_once(|| unsafe {
|
||||
let superclass = class!(NSColor);
|
||||
let mut decl = ClassDecl::new("CacaoDynamicColor", superclass).unwrap();
|
||||
|
||||
// These methods all need to be forwarded, so let's hook them up.
|
||||
decl.add_method(sel!(colorSpace), color_space as extern fn(&Object, _) -> id);
|
||||
decl.add_method(sel!(colorUsingColorSpace:), color_using_color_space as extern fn(&Object, _, id) -> id);
|
||||
decl.add_method(sel!(colorSpaceName), color_space_name as extern fn(&Object, _) -> id);
|
||||
decl.add_method(sel!(colorUsingColorSpaceName:), color_using_color_space_name as extern fn(&Object, _, id) -> id);
|
||||
decl.add_method(sel!(numberOfComponents), number_of_components as extern fn(&Object, _) -> NSInteger);
|
||||
|
||||
decl.add_method(sel!(getComponents:), get_components as extern fn(&Object, _, CGFloat));
|
||||
decl.add_method(sel!(getRed:green:blue:alpha:), get_rgba as extern fn(&Object, _, CGFloat, CGFloat, CGFloat, CGFloat));
|
||||
decl.add_method(sel!(redComponent), red_component as extern fn(&Object, _) -> CGFloat);
|
||||
decl.add_method(sel!(greenComponent), green_component as extern fn(&Object, _) -> CGFloat);
|
||||
decl.add_method(sel!(blueComponent), blue_component as extern fn(&Object, _) -> CGFloat);
|
||||
|
||||
decl.add_method(sel!(hueComponent), hue_component as extern fn(&Object, _) -> CGFloat);
|
||||
decl.add_method(sel!(saturationComponent), saturation_component as extern fn(&Object, _) -> CGFloat);
|
||||
decl.add_method(sel!(brightnessComponent), brightness_component as extern fn(&Object, _) -> CGFloat);
|
||||
decl.add_method(sel!(getHue:saturation:brightness:alpha:), get_hsba as extern fn(&Object, _, CGFloat, CGFloat, CGFloat, CGFloat));
|
||||
|
||||
decl.add_method(sel!(whiteComponent), white_component as extern fn(&Object, _) -> CGFloat);
|
||||
decl.add_method(sel!(getWhite:alpha:), get_white as extern fn(&Object, _, CGFloat, CGFloat));
|
||||
|
||||
decl.add_method(sel!(cyanComponent), cyan_component as extern fn(&Object, _) -> CGFloat);
|
||||
decl.add_method(sel!(magentaComponent), magenta_component as extern fn(&Object, _) -> CGFloat);
|
||||
decl.add_method(sel!(yellowComponent), yellow_component as extern fn(&Object, _) -> CGFloat);
|
||||
decl.add_method(sel!(blackComponent), black_component as extern fn(&Object, _) -> CGFloat);
|
||||
decl.add_method(sel!(getCyan:magenta:yellow:black:alpha:), get_cmyk as extern fn(&Object, _, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat));
|
||||
|
||||
decl.add_method(sel!(alphaComponent), alpha_component as extern fn(&Object, _) -> CGFloat);
|
||||
|
||||
decl.add_method(sel!(CGColor), cg_color as extern fn(&Object, _) -> id);
|
||||
decl.add_method(sel!(setStroke), set_stroke as extern fn(&Object, _));
|
||||
decl.add_method(sel!(setFill), set_fill as extern fn(&Object, _));
|
||||
decl.add_method(sel!(set), call_set as extern fn(&Object, _));
|
||||
|
||||
decl.add_method(sel!(highlightWithLevel:), highlight_with_level as extern fn(&Object, _, CGFloat) -> id);
|
||||
decl.add_method(sel!(shadowWithLevel:), shadow_with_level as extern fn(&Object, _, CGFloat) -> id);
|
||||
|
||||
decl.add_method(sel!(colorWithAlphaComponent:), color_with_alpha_component as extern fn(&Object, _, CGFloat) -> id);
|
||||
decl.add_method(sel!(blendedColorWithFraction:ofColor:), blended_color as extern fn(&Object, _, CGFloat, id) -> id);
|
||||
decl.add_method(sel!(colorWithSystemEffect:), color_with_system_effect as extern fn(&Object, _, NSInteger) -> id);
|
||||
|
||||
decl.add_ivar::<id>(AQUA_LIGHT_COLOR_NORMAL_CONTRAST);
|
||||
decl.add_ivar::<id>(AQUA_LIGHT_COLOR_HIGH_CONTRAST);
|
||||
decl.add_ivar::<id>(AQUA_DARK_COLOR_NORMAL_CONTRAST);
|
||||
decl.add_ivar::<id>(AQUA_DARK_COLOR_HIGH_CONTRAST);
|
||||
|
||||
VIEW_CLASS = decl.register();
|
||||
});
|
||||
|
||||
unsafe { VIEW_CLASS }
|
||||
}
|
488
src/color/mod.rs
Normal file
488
src/color/mod.rs
Normal file
|
@ -0,0 +1,488 @@
|
|||
//! Implements a wrapper type for `NSColor` and `UIColor`. It attempts to map
|
||||
//! to a common shared API, but it's important to note that the platforms
|
||||
//! themselves have differing levels of support for color work. Where possible,
|
||||
//! we expose some platform-specific methods for creating and working with these.
|
||||
//!
|
||||
//! We attempt to provide fallbacks for older versions of macOS/iOS, but this is not exhaustive,
|
||||
/// as the cross-section of people building for older platforms in Rust is likely very low. If you
|
||||
/// need these fallbacks to be better and/or correct, you're welcome to improve and pull-request
|
||||
/// this.
|
||||
///
|
||||
/// The goal here is to make sure that this can't reasonably break on OS's, as `Color` is kind of
|
||||
/// an important piece. It's not on the framework to make your app look good, though. To enable
|
||||
/// fallbacks, specify the `color_fallbacks` feature in your `Cargo.toml`.
|
||||
///
|
||||
/// @TODO: bundle iOS/tvOS support.
|
||||
|
||||
use core_graphics::base::CGFloat;
|
||||
use core_graphics::color::CGColor;
|
||||
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc::runtime::Object;
|
||||
use objc_id::ShareId;
|
||||
|
||||
use crate::foundation::id;
|
||||
use crate::utils::os;
|
||||
|
||||
#[cfg(feature = "macos")]
|
||||
mod macos_dynamic_color;
|
||||
|
||||
#[cfg(feature = "macos")]
|
||||
use macos_dynamic_color::{
|
||||
AQUA_LIGHT_COLOR_NORMAL_CONTRAST, AQUA_LIGHT_COLOR_HIGH_CONTRAST,
|
||||
AQUA_DARK_COLOR_NORMAL_CONTRAST, AQUA_DARK_COLOR_HIGH_CONTRAST
|
||||
};
|
||||
|
||||
/// Represents a rendering style - dark mode or light mode.
|
||||
/// In the event that a new variant is introduced in later versions of
|
||||
/// macOS or iOS, calls that use the dynamic color(s) from here will likely
|
||||
/// default to the `Light` theme.
|
||||
#[derive(Debug)]
|
||||
pub enum Theme {
|
||||
/// The "default" theme on a platform. On macOS, this is Aqua.
|
||||
/// On iOS and tvOS, this is whatever you call the system defined theme.
|
||||
Light,
|
||||
|
||||
/// Dark mode.
|
||||
Dark
|
||||
}
|
||||
|
||||
/// Represents the contrast level for a rendering context.
|
||||
#[derive(Debug)]
|
||||
pub enum Contrast {
|
||||
/// The default contrast level for the system.
|
||||
Normal,
|
||||
|
||||
/// The high contrast level for the system.
|
||||
High
|
||||
}
|
||||
|
||||
/// A `Style` is passed to you when doing dynamic color calculations. You can opt to
|
||||
/// provide different colors depending on the settings in here - notably, this is useful
|
||||
/// for supporting dark mode and high contrast accessibility contexts.
|
||||
#[derive(Debug)]
|
||||
pub struct Style {
|
||||
/// Represents the current theme for where this color may render.
|
||||
pub theme: Theme,
|
||||
|
||||
/// Represents the current contrast level for where this color may render.
|
||||
pub contrast: Contrast
|
||||
}
|
||||
|
||||
/*
|
||||
#[derive(Clone)]
|
||||
pub struct Property(Rc<RefCell<Id<Object>>>);
|
||||
|
||||
impl Property {
|
||||
pub fn new(obj: id) -> Self {
|
||||
Property(Rc::new(RefCell::new(Id::from_ptr(obj))))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ThreadSafeProperty(Arc<RwLock<Id<Object>>>);
|
||||
|
||||
impl Property {
|
||||
pub fn new(obj: id) -> Self {
|
||||
Property(Rc::new(RefCell::new(Id::from_ptr(obj))))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/// Represents a Color. You can create custom colors using the various
|
||||
/// initializers, or opt to use a system-provided color. The system provided
|
||||
/// colors will automatically switch to the "correct" colors/shades depending on whether
|
||||
/// the user is in light or dark mode; to support this with custom colors, be sure
|
||||
/// to call the `.dark()` method after initializing.
|
||||
#[derive(Clone)]
|
||||
pub enum Color {
|
||||
/// Represents an `NSColor` on macOS, and a `UIColor` everywhere else. You typically
|
||||
/// don't create this variant yourself; use the initializers found on this enum.
|
||||
///
|
||||
/// If you need to do custom work not covered by this enum, you can drop to
|
||||
/// the Objective-C level yourself and wrap your color in this.
|
||||
Object(ShareId<Object>),
|
||||
|
||||
/// The system-provided black. Harsh - you probably don't want to use this.
|
||||
SystemBlack,
|
||||
|
||||
/// The system-provided absolute white.
|
||||
SystemWhite,
|
||||
|
||||
/// The system-provided brown.
|
||||
SystemBrown,
|
||||
|
||||
/// The system-provided blue.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemBlue,
|
||||
|
||||
/// The system-provided green.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemGreen,
|
||||
|
||||
/// The system-provided indigo.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemIndigo,
|
||||
|
||||
/// The system-provided orange.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemOrange,
|
||||
|
||||
/// The system-provided pink.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemPink,
|
||||
|
||||
/// The system-provided purple.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemPurple,
|
||||
|
||||
/// The system-provided red.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemRed,
|
||||
|
||||
/// The system-provided teal.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemTeal,
|
||||
|
||||
/// The system-provided yellow.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemYellow,
|
||||
|
||||
/// The system-provided base gray color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemGray,
|
||||
|
||||
/// The system-provided secondary-level gray color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemGray2,
|
||||
|
||||
/// The system-provided third-level gray color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemGray3,
|
||||
|
||||
/// The system-provided fourth-level gray color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemGray4,
|
||||
|
||||
/// The system-provided fifth-level gray color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemGray5,
|
||||
|
||||
/// The system-provided sixth-level gray color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemGray6,
|
||||
|
||||
/// Represents a clear color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
Clear,
|
||||
|
||||
/// The default label color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
Label,
|
||||
|
||||
/// The default color for a second-level label.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
LabelSecondary,
|
||||
|
||||
/// The default color for a third-level label.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
LabelTertiary,
|
||||
|
||||
/// The default color for a fourth-level label.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
LabelQuaternary,
|
||||
|
||||
/// The default system fill color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemFill,
|
||||
|
||||
/// The default system second-level fill color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemFillSecondary,
|
||||
|
||||
/// The default system third-level fill color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemFillTertiary,
|
||||
|
||||
/// The default system fourth-level fill color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemFillQuaternary,
|
||||
|
||||
/// The default color to use for placeholder text.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
PlaceholderText,
|
||||
|
||||
/// The default system background color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemBackground,
|
||||
|
||||
/// The default system second-level background color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemBackgroundSecondary,
|
||||
|
||||
/// The default system third-level background color.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
SystemBackgroundTertiary,
|
||||
|
||||
/// The default color to use for thin separators/lines that
|
||||
/// still allow content to be visible underneath.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
Separator,
|
||||
|
||||
/// The default color to use for thin separators/lines that
|
||||
/// do not allow content underneath to be visible.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
OpaqueSeparator,
|
||||
|
||||
/// The default color to use for rendering links.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
Link,
|
||||
|
||||
/// The un-adaptable color for text on a light background.
|
||||
DarkText,
|
||||
|
||||
/// The un-adaptable color for text on a dark background.
|
||||
LightText
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Creates and returns a color in the RGB space, with the specified
|
||||
/// alpha level.
|
||||
pub fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
|
||||
let r = red as CGFloat / 255.0;
|
||||
let g = green as CGFloat / 255.0;
|
||||
let b = blue as CGFloat / 255.0;
|
||||
let a = alpha as CGFloat / 255.0;
|
||||
|
||||
#[cfg(feature = "macos")]
|
||||
let color = class!(NSColor);
|
||||
|
||||
#[cfg(feature = "ios")]
|
||||
let color = class!(UIColor);
|
||||
|
||||
Color::Object(unsafe {
|
||||
#[cfg(feature = "macos")]
|
||||
ShareId::from_ptr(msg_send![color, colorWithCalibratedRed:r green:g blue:b alpha:a])
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates and returns a color in the RGB space, with the alpha level
|
||||
/// set to `255` by default. Shorthand for `rgba`.
|
||||
pub fn rgb(red: u8, green: u8, blue: u8) -> Self {
|
||||
Color::rgba(red, green, blue, 255)
|
||||
}
|
||||
|
||||
/// Creates and returns a color in the HSB space, with the specified
|
||||
/// alpha level.
|
||||
pub fn hsba(hue: u8, saturation: u8, brightness: u8, alpha: u8) -> Self {
|
||||
let h = hue as CGFloat / 255.0;
|
||||
let s = saturation as CGFloat / 255.0;
|
||||
let b = brightness as CGFloat / 255.0;
|
||||
let a = alpha as CGFloat / 255.0;
|
||||
|
||||
#[cfg(feature = "macos")]
|
||||
let color = class!(NSColor);
|
||||
|
||||
#[cfg(feature = "ios")]
|
||||
let color = class!(UIColor);
|
||||
|
||||
Color::Object(unsafe {
|
||||
#[cfg(feature = "macos")]
|
||||
ShareId::from_ptr(msg_send![color, colorWithCalibratedHue:h saturation:s brightness:b alpha:a])
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates and returns a color in the RGB space, with the alpha level
|
||||
/// set to `255` by default. Shorthand for `hsba`.
|
||||
pub fn hsb(hue: u8, saturation: u8, brightness: u8) -> Self {
|
||||
Color::hsba(hue, saturation, brightness, 255)
|
||||
}
|
||||
|
||||
/// Creates and returns a white color with the specified level or intensity, along with the
|
||||
/// specified alpha.
|
||||
pub fn white_alpha(level: CGFloat, alpha: CGFloat) -> Self {
|
||||
#[cfg(feature = "macos")]
|
||||
let color = class!(NSColor);
|
||||
|
||||
#[cfg(feature = "ios")]
|
||||
let color = class!(UIColor);
|
||||
|
||||
Color::Object(unsafe {
|
||||
#[cfg(feature = "macos")]
|
||||
ShareId::from_ptr(msg_send![color, colorWithCalibratedWhite:level alpha:alpha])
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates and returns a white Color with the specified level or intensity, with the alpha
|
||||
/// value set to `255`. Shorthand for `white_alpha`.
|
||||
pub fn white(level: CGFloat) -> Self {
|
||||
Color::white_alpha(level, 1.0)
|
||||
}
|
||||
|
||||
/// Given a hex code and alpha level, returns a `Color` in the RGB space.
|
||||
///
|
||||
/// This method is not an ideal one to use, but is offered as a convenience method for those
|
||||
/// coming from other environments where these are more common.
|
||||
pub fn hexa(hex: &str, alpha: u8) -> Self {
|
||||
Color::SystemRed
|
||||
}
|
||||
|
||||
/// Given a hex code, returns a `Color` in the RGB space with alpha pre-set to `255`.
|
||||
///
|
||||
/// This method is not an ideal one to use, but is offered as a convenience method for those
|
||||
/// coming from other environments where these are more common.
|
||||
pub fn hex(hex: &str) -> Self {
|
||||
Color::hexa(hex, 255)
|
||||
}
|
||||
|
||||
/// Creates and returns a dynamic color, which stores a handler and enables returning specific
|
||||
/// colors at appearance time based on device traits (i.e, dark mode vs light mode, contrast
|
||||
/// settings, etc).
|
||||
///
|
||||
/// For systems that don't support dark mode (macOS pre-Mojave) this will always paint with the
|
||||
/// "default" or "light" color.
|
||||
///
|
||||
/// Returning a dynamic color in your handler is unsupported and may panic.
|
||||
pub fn dynamic<F>(handler: F) -> Self
|
||||
where
|
||||
F: Fn(Style) -> Color + 'static
|
||||
{
|
||||
#[cfg(feature = "macos")]
|
||||
Color::Object(unsafe {
|
||||
let color: id = msg_send![macos_dynamic_color::register_class(), new];
|
||||
|
||||
(&mut *color).set_ivar(AQUA_LIGHT_COLOR_NORMAL_CONTRAST, handler(Style {
|
||||
theme: Theme::Light,
|
||||
contrast: Contrast::Normal
|
||||
}).to_objc());
|
||||
|
||||
(&mut *color).set_ivar(AQUA_LIGHT_COLOR_HIGH_CONTRAST, handler(Style {
|
||||
theme: Theme::Light,
|
||||
contrast: Contrast::High
|
||||
}).to_objc());
|
||||
|
||||
(&mut *color).set_ivar(AQUA_DARK_COLOR_NORMAL_CONTRAST, handler(Style {
|
||||
theme: Theme::Dark,
|
||||
contrast: Contrast::Normal
|
||||
}).to_objc());
|
||||
|
||||
(&mut *color).set_ivar(AQUA_DARK_COLOR_HIGH_CONTRAST, handler(Style {
|
||||
theme: Theme::Light,
|
||||
contrast: Contrast::Normal
|
||||
}).to_objc());
|
||||
|
||||
ShareId::from_ptr(color)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a pointer that can be used for the Objective-C runtime.
|
||||
///
|
||||
/// This method is primarily for internal use, but is kept public for those who might need to
|
||||
/// work with colors outside of what's available in this enum.
|
||||
pub fn to_objc(&self) -> id {
|
||||
unsafe { to_objc(self) }
|
||||
}
|
||||
|
||||
/// Legacy.
|
||||
pub fn into_platform_specific_color(&self) -> id {
|
||||
unsafe { to_objc(self) }
|
||||
}
|
||||
|
||||
/// Returns a CGColor, which can be used in Core Graphics calls as well as other areas.
|
||||
///
|
||||
/// Note that CGColor is _not_ a context-aware color, unlike our `NSColor` and `UIColor`
|
||||
/// objects. If you're painting in a context that requires dark mode support, make sure
|
||||
/// you're not using a cached version of this unless you explicitly want the _same_ color
|
||||
/// in every context it's used in.
|
||||
pub fn cg_color(&self) -> CGColor {
|
||||
// @TODO: This should probably return a CGColorRef...
|
||||
unsafe {
|
||||
let objc = to_objc(self);
|
||||
msg_send![objc, CGColor]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Color> for Color {
|
||||
/// Provided to make passing `Color` types around less of a headache.
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &Color {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles color fallback for system-provided colors.
|
||||
macro_rules! system_color_with_fallback {
|
||||
($class:ident, $color:ident, $fallback:ident) => ({
|
||||
#[cfg(feature = "macos")]
|
||||
{
|
||||
#[cfg(feature = "color_fallbacks")]
|
||||
if os::minimum_semversion(10, 10, 0) {
|
||||
msg_send![$class, $color]
|
||||
} else {
|
||||
msg_send![$class, $fallback]
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color_fallbacks"))]
|
||||
msg_send![$class, $color]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// This function maps enum types to system-provided colors, or our stored NS/UIColor objects.
|
||||
/// It attempts to provide fallbacks for older versions of macOS/iOS, but this is not exhaustive,
|
||||
/// as the cross-section of people building for older platforms in Rust is likely very low. If you
|
||||
/// need these fallbacks to be better and/or correct, you're welcome to improve and pull-request
|
||||
/// this.
|
||||
///
|
||||
/// The goal here is to make sure that this can't reasonably break on OS's, as `Color` is kind of
|
||||
/// an important piece. It's not on the framework to make your app look good, though.
|
||||
unsafe fn to_objc(obj: &Color) -> id {
|
||||
#[cfg(feature = "macos")]
|
||||
let color = class!(NSColor);
|
||||
|
||||
#[cfg(feature = "ios")]
|
||||
let color = class!(UIColor);
|
||||
|
||||
match obj {
|
||||
// Regardless of platform, we can just dereference this one.
|
||||
Color::Object(obj) => msg_send![&**obj, self],
|
||||
|
||||
Color::SystemBlack => msg_send![color, blackColor],
|
||||
Color::SystemWhite => msg_send![color, whiteColor],
|
||||
Color::SystemBrown => msg_send![color, brownColor],
|
||||
Color::SystemBlue => system_color_with_fallback!(color, systemBlueColor, blueColor),
|
||||
Color::SystemGreen => system_color_with_fallback!(color, systemGreenColor, greenColor),
|
||||
Color::SystemIndigo => system_color_with_fallback!(color, systemIndigoColor, magentaColor),
|
||||
Color::SystemOrange => system_color_with_fallback!(color, systemOrangeColor, orangeColor),
|
||||
Color::SystemPink => system_color_with_fallback!(color, systemPinkColor, pinkColor),
|
||||
Color::SystemPurple => system_color_with_fallback!(color, systemPurpleColor, purpleColor),
|
||||
Color::SystemRed => system_color_with_fallback!(color, systemRedColor, redColor),
|
||||
Color::SystemTeal => system_color_with_fallback!(color, systemTealColor, blueColor),
|
||||
Color::SystemYellow => system_color_with_fallback!(color, systemYellowColor, yellowColor),
|
||||
Color::SystemGray => system_color_with_fallback!(color, systemGrayColor, darkGrayColor),
|
||||
Color::SystemGray2 => system_color_with_fallback!(color, systemGray2Color, grayColor),
|
||||
Color::SystemGray3 => system_color_with_fallback!(color, systemGray3Color, lightGrayColor),
|
||||
Color::SystemGray4 => system_color_with_fallback!(color, systemGray4Color, lightGrayColor),
|
||||
Color::SystemGray5 => system_color_with_fallback!(color, systemGray5Color, lightGrayColor),
|
||||
Color::SystemGray6 => system_color_with_fallback!(color, systemGray6Color, lightGrayColor),
|
||||
Color::Clear => msg_send![color, clearColor],
|
||||
Color::Label => system_color_with_fallback!(color, labelColor, blackColor),
|
||||
Color::LabelSecondary => system_color_with_fallback!(color, secondaryLabelColor, blackColor),
|
||||
Color::LabelTertiary => system_color_with_fallback!(color, tertiaryLabelColor, blackColor),
|
||||
Color::LabelQuaternary => system_color_with_fallback!(color, quaternaryLabelColor, blackColor),
|
||||
Color::SystemFill => system_color_with_fallback!(color, systemFillColor, clearColor),
|
||||
Color::SystemFillSecondary => system_color_with_fallback!(color, secondarySystemFillColor, clearColor),
|
||||
Color::SystemFillTertiary => system_color_with_fallback!(color, tertiarySystemFillColor, clearColor),
|
||||
Color::SystemFillQuaternary => system_color_with_fallback!(color, quaternarySystemFillColor, clearColor),
|
||||
Color::PlaceholderText => system_color_with_fallback!(color, placeholderTextColor, darkGrayColor),
|
||||
Color::SystemBackground => system_color_with_fallback!(color, systemBackgroundColor, clearColor),
|
||||
Color::SystemBackgroundSecondary => system_color_with_fallback!(color, secondarySystemBackgroundColor, clearColor),
|
||||
Color::SystemBackgroundTertiary => system_color_with_fallback!(color, tertiarySystemBackgroundColor, clearColor),
|
||||
Color::Separator => system_color_with_fallback!(color, separatorColor, lightGrayColor),
|
||||
Color::OpaqueSeparator => system_color_with_fallback!(color, opaqueSeparatorColor, darkGrayColor),
|
||||
Color::Link => system_color_with_fallback!(color, linkColor, blueColor),
|
||||
Color::DarkText => system_color_with_fallback!(color, darkTextColor, blackColor),
|
||||
Color::LightText => system_color_with_fallback!(color, lightTextColor, whiteColor),
|
||||
}
|
||||
}
|
214
src/layout/attributes.rs
Normal file
214
src/layout/attributes.rs
Normal file
|
@ -0,0 +1,214 @@
|
|||
use crate::foundation::{NSInteger, NSUInteger};
|
||||
|
||||
/// Represents whether a layout is vertical or horizontal.
|
||||
#[derive(Debug)]
|
||||
pub enum LayoutConstraintOrientation {
|
||||
/// Horizontal orientation.
|
||||
Horizontal,
|
||||
|
||||
/// Vertical orientation.
|
||||
Vertical,
|
||||
|
||||
/// Represents an unknown value. This should never be constructed, but acts as a guard against
|
||||
/// a change in representation on the framework side. If a new value was ever introduced, it's
|
||||
/// caught here, and applications can handle it themselves if need be.
|
||||
Unknown(NSInteger)
|
||||
}
|
||||
|
||||
impl From<NSInteger> for LayoutConstraintOrientation {
|
||||
fn from(i: NSInteger) -> Self {
|
||||
match i {
|
||||
0 => Self::Horizontal,
|
||||
1 => Self::Vertical,
|
||||
i => Self::Unknown(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a relation between layout constraints. Used mostly internally.
|
||||
#[derive(Debug)]
|
||||
pub enum LayoutRelation {
|
||||
/// Relation is less than or equal to another specified relation.
|
||||
LessThanOrEqual,
|
||||
|
||||
/// Relation is equal to another specified relation.
|
||||
Equal,
|
||||
|
||||
/// Relation is greater than or equal to another specified relation.
|
||||
GreaterThanOrEqual,
|
||||
|
||||
/// Represents an unknown value. This should never be constructed, but acts as a guard against
|
||||
/// a change in representation on the framework side. If a new value was ever introduced, it's
|
||||
/// caught here, and applications can handle it themselves if need be.
|
||||
Unknown(NSInteger)
|
||||
}
|
||||
|
||||
impl From<NSInteger> for LayoutRelation {
|
||||
fn from(i: NSInteger) -> Self {
|
||||
match i {
|
||||
-1 => Self::LessThanOrEqual,
|
||||
0 => Self::Equal,
|
||||
1 => Self::GreaterThanOrEqual,
|
||||
i => Self::Unknown(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents attributes for various layouts and constraints.
|
||||
///
|
||||
/// Note that this only covers attributes that are shared across platforms. In general, this is enough
|
||||
/// to build apps that work everywhere - but if you need to specify something else, you can handle
|
||||
/// it yourself with the `Unknown` variant.
|
||||
#[derive(Debug)]
|
||||
pub enum LayoutAttribute {
|
||||
/// The left side of the object’s alignment rectangle.
|
||||
Left,
|
||||
|
||||
/// The right side of the object’s alignment rectangle.
|
||||
Right,
|
||||
|
||||
/// The top of the object’s alignment rectangle.
|
||||
Top,
|
||||
|
||||
/// The bottom of the object’s alignment rectangle.
|
||||
Bottom,
|
||||
|
||||
/// The leading edge of the object’s alignment rectangle.
|
||||
Leading,
|
||||
|
||||
/// The trailing edge of the object’s alignment rectangle.
|
||||
Trailing,
|
||||
|
||||
/// The width of the object’s alignment rectangle.
|
||||
Width,
|
||||
|
||||
/// The height of the object’s alignment rectangle.
|
||||
Height,
|
||||
|
||||
/// The center along the x-axis of the object’s alignment rectangle.
|
||||
CenterX,
|
||||
|
||||
/// The center along the y-axis of the object’s alignment rectangle.
|
||||
CenterY,
|
||||
|
||||
/// The object’s baseline. For objects with more than one line of text,
|
||||
/// this is the baseline for the bottommost line of text.
|
||||
LastBaseline,
|
||||
|
||||
/// The object’s baseline. For objects with more than one line of text,
|
||||
/// this is the baseline for the topmost line of text.
|
||||
FirstBaseline,
|
||||
|
||||
/// A placeholder value that is used to indicate that the constraint’s
|
||||
/// second item and second attribute are not used in any calculations.
|
||||
///
|
||||
/// This can be useful constraint that assigns a constant to an attribute.
|
||||
NotAnAttribute,
|
||||
|
||||
/// Represents an unknown value. This should never be constructed, but acts as a guard against
|
||||
/// a change in representation on the framework side. If a new value was ever introduced, it's
|
||||
/// caught here, and applications can handle it themselves if need be.
|
||||
Unknown(NSInteger)
|
||||
}
|
||||
|
||||
impl From<NSInteger> for LayoutAttribute {
|
||||
fn from(i: NSInteger) -> Self {
|
||||
match i {
|
||||
1 => Self::Left,
|
||||
2 => Self::Right,
|
||||
3 => Self::Top,
|
||||
4 => Self::Bottom,
|
||||
5 => Self::Leading,
|
||||
6 => Self::Trailing,
|
||||
7 => Self::Width,
|
||||
8 => Self::Height,
|
||||
9 => Self::CenterX,
|
||||
10 => Self::CenterY,
|
||||
11 => Self::LastBaseline,
|
||||
12 => Self::FirstBaseline,
|
||||
0 => Self::NotAnAttribute,
|
||||
i => Self::Unknown(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a layout format.
|
||||
///
|
||||
/// Note that this only covers formats that are shared across platforms. In general, this is enough
|
||||
/// to build apps that work everywhere - but if you need to specify something else, you can handle
|
||||
/// it yourself with the `Unknown` variant.
|
||||
#[derive(Debug)]
|
||||
pub enum LayoutFormat {
|
||||
/// Align all specified interface elements using NSLayoutAttributeLeft on each.
|
||||
AlignAllLeft,
|
||||
|
||||
/// Align all specified interface elements using NSLayoutAttributeRight on each.
|
||||
AlignAllRight,
|
||||
|
||||
/// Align all specified interface elements using NSLayoutAttributeTop on each.
|
||||
AlignAllTop,
|
||||
|
||||
/// Align all specified interface elements using NSLayoutAttributeBottom on each.
|
||||
AlignAllBottom,
|
||||
|
||||
/// Align all specified interface elements using NSLayoutAttributeLeading on each.
|
||||
AlignAllLeading,
|
||||
|
||||
/// Align all specified interface elements using NSLayoutAttributeTrailing on each.
|
||||
AlignAllTrailing,
|
||||
|
||||
/// Align all specified interface elements using NSLayoutAttributeCenterX on each.
|
||||
AlignAllCenterX,
|
||||
|
||||
/// Align all specified interface elements using NSLayoutAttributeCenterY on each.
|
||||
AlignAllCenterY,
|
||||
|
||||
/// Align all specified interface elements using the last baseline of each one.
|
||||
AlignAllLastBaseline,
|
||||
|
||||
/// Arrange objects in order based on the normal text flow for the current user
|
||||
/// interface language. In left-to-right languages (like English), this arrangement
|
||||
/// results in the first object being placed farthest to the left, the next one to
|
||||
/// its right, and so on. In right-to-left languages (like Arabic or Hebrew), the
|
||||
/// ordering is reversed.
|
||||
DirectionLeadingToTrailing,
|
||||
|
||||
/// Arrange objects in order from left to right.
|
||||
DirectionLeftToRight,
|
||||
|
||||
/// Arrange objects in order from right to left.
|
||||
DirectionRightToLeft,
|
||||
|
||||
/// Represents an unknown value. This should never be constructed, but acts as a guard against
|
||||
/// a change in representation on the framework side. If a new value was ever introduced, it's
|
||||
/// caught here, and applications can handle it themselves if need be.
|
||||
Unknown(NSUInteger)
|
||||
}
|
||||
|
||||
impl From<NSUInteger> for LayoutFormat {
|
||||
fn from(i: NSUInteger) -> Self {
|
||||
match i {
|
||||
2 => Self::AlignAllLeft,
|
||||
4 => Self::AlignAllRight,
|
||||
8 => Self::AlignAllTop,
|
||||
16 => Self::AlignAllBottom,
|
||||
32 => Self::AlignAllLeading,
|
||||
64 => Self::AlignAllTrailing,
|
||||
512 => Self::AlignAllCenterX,
|
||||
1024 => Self::AlignAllCenterY,
|
||||
2048 => Self::AlignAllLastBaseline,
|
||||
0 => Self::DirectionLeadingToTrailing,
|
||||
65536 => Self::DirectionLeftToRight,
|
||||
131072 => Self::DirectionRightToLeft,
|
||||
i => Self::Unknown(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies layout priority.
|
||||
#[derive(Debug)]
|
||||
pub enum LayoutPriority {
|
||||
Required,
|
||||
High,
|
||||
Low
|
||||
}
|
|
@ -39,7 +39,7 @@ impl LayoutAnchorDimension {
|
|||
pub fn constraint_equal_to(&self, anchor_to: &LayoutAnchorDimension) -> LayoutConstraint {
|
||||
match (&self.0, &anchor_to.0) {
|
||||
(Some(from), Some(to)) => LayoutConstraint::new(unsafe {
|
||||
msg_send![*from, constraintEqualToAnchor:&*to]
|
||||
msg_send![*from, constraintEqualToAnchor:&**to]
|
||||
}),
|
||||
|
||||
_ => { panic!("Attempted to create constraints with an uninitialized anchor!"); }
|
||||
|
@ -50,7 +50,7 @@ impl LayoutAnchorDimension {
|
|||
pub fn constraint_greater_than_or_equal_to(&self, anchor_to: &LayoutAnchorDimension) -> LayoutConstraint {
|
||||
match (&self.0, &anchor_to.0) {
|
||||
(Some(from), Some(to)) => LayoutConstraint::new(unsafe {
|
||||
msg_send![*from, constraintGreaterThanOrEqualToAnchor:&*to]
|
||||
msg_send![*from, constraintGreaterThanOrEqualToAnchor:&**to]
|
||||
}),
|
||||
|
||||
_ => { panic!("Attempted to create constraints with an uninitialized anchor!"); }
|
||||
|
@ -61,7 +61,7 @@ impl LayoutAnchorDimension {
|
|||
pub fn constraint_less_than_or_equal_to(&self, anchor_to: &LayoutAnchorDimension) -> LayoutConstraint {
|
||||
match (&self.0, &anchor_to.0) {
|
||||
(Some(from), Some(to)) => LayoutConstraint::new(unsafe {
|
||||
msg_send![*from, constraintLessThanOrEqualToAnchor:&*to]
|
||||
msg_send![*from, constraintLessThanOrEqualToAnchor:&**to]
|
||||
}),
|
||||
|
||||
_ => { panic!("Attempted to create constraints with an uninitialized anchor!"); }
|
||||
|
|
|
@ -26,7 +26,7 @@ impl LayoutAnchorX {
|
|||
pub fn constraint_equal_to(&self, anchor_to: &LayoutAnchorX) -> LayoutConstraint {
|
||||
match (&self.0, &anchor_to.0) {
|
||||
(Some(from), Some(to)) => LayoutConstraint::new(unsafe {
|
||||
msg_send![*from, constraintEqualToAnchor:&*to.clone()]
|
||||
msg_send![*from, constraintEqualToAnchor:&**to]
|
||||
}),
|
||||
|
||||
_ => { panic!("Attempted to create horizontal constraints with an uninitialized anchor!"); }
|
||||
|
@ -37,7 +37,7 @@ impl LayoutAnchorX {
|
|||
pub fn constraint_greater_than_or_equal_to(&self, anchor_to: &LayoutAnchorX) -> LayoutConstraint {
|
||||
match (&self.0, &anchor_to.0) {
|
||||
(Some(from), Some(to)) => LayoutConstraint::new(unsafe {
|
||||
msg_send![*from, constraintGreaterThanOrEqualToAnchor:&*to]
|
||||
msg_send![*from, constraintGreaterThanOrEqualToAnchor:&**to]
|
||||
}),
|
||||
|
||||
_ => { panic!("Attempted to create horizontal constraints with an uninitialized anchor!"); }
|
||||
|
@ -48,7 +48,7 @@ impl LayoutAnchorX {
|
|||
pub fn constraint_less_than_or_equal_to(&self, anchor_to: &LayoutAnchorX) -> LayoutConstraint {
|
||||
match (&self.0, &anchor_to.0) {
|
||||
(Some(from), Some(to)) => LayoutConstraint::new(unsafe {
|
||||
msg_send![*from, constraintLessThanOrEqualToAnchor:&*to]
|
||||
msg_send![*from, constraintLessThanOrEqualToAnchor:&**to]
|
||||
}),
|
||||
|
||||
_ => { panic!("Attempted to create horizontal constraints with an uninitialized anchor!"); }
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
//! not be able to attach a left-axis to a top-axis. In Rust this is a bit tricky, but by using
|
||||
//! some `impl Trait`'s in the right places we can mostly hide this detail away.
|
||||
|
||||
pub mod attributes;
|
||||
pub use attributes::*;
|
||||
|
||||
pub mod traits;
|
||||
pub use traits::Layout;
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ impl LayoutAnchorY {
|
|||
pub fn constraint_equal_to(&self, anchor_to: &LayoutAnchorY) -> LayoutConstraint {
|
||||
match (&self.0, &anchor_to.0) {
|
||||
(Some(from), Some(to)) => LayoutConstraint::new(unsafe {
|
||||
let b: id = msg_send![*from, constraintEqualToAnchor:&*to.clone()];
|
||||
let b: id = msg_send![*from, constraintEqualToAnchor:&**to];
|
||||
b
|
||||
}),
|
||||
|
||||
|
@ -38,7 +38,7 @@ impl LayoutAnchorY {
|
|||
pub fn constraint_greater_than_or_equal_to(&self, anchor_to: &LayoutAnchorY) -> LayoutConstraint {
|
||||
match (&self.0, &anchor_to.0) {
|
||||
(Some(from), Some(to)) => LayoutConstraint::new(unsafe {
|
||||
msg_send![*from, constraintGreaterThanOrEqualToAnchor:&*to]
|
||||
msg_send![*from, constraintGreaterThanOrEqualToAnchor:&**to]
|
||||
}),
|
||||
|
||||
_ => { panic!("Attempted to create vertical constraints with an uninitialized anchor!"); }
|
||||
|
@ -49,7 +49,7 @@ impl LayoutAnchorY {
|
|||
pub fn constraint_less_than_or_equal_to(&self, anchor_to: &LayoutAnchorY) -> LayoutConstraint {
|
||||
match (&self.0, &anchor_to.0) {
|
||||
(Some(from), Some(to)) => LayoutConstraint::new(unsafe {
|
||||
msg_send![*from, constraintLessThanOrEqualToAnchor:&*to]
|
||||
msg_send![*from, constraintLessThanOrEqualToAnchor:&**to]
|
||||
}),
|
||||
|
||||
_ => { panic!("Attempted to create vertical constraints with an uninitialized anchor!"); }
|
||||
|
|
|
@ -48,8 +48,12 @@ extern fn view_for_column<T: ListViewDelegate>(
|
|||
// @TODO: probably find a better way to do this. It's theoretically fine,
|
||||
// as we *know* the underlying view will be retained by the NSTableView, so
|
||||
// passing over one more won't really screw up retain counts.
|
||||
//
|
||||
// @TODO: Finish investing the `Rc` approach, might be able to just take
|
||||
// ownership and rely on Rust being correct.
|
||||
let objc = item.objc.borrow();
|
||||
unsafe {
|
||||
msg_send![&*item.objc, self]
|
||||
msg_send![&**objc, self]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,10 @@
|
|||
//!
|
||||
//! For more information on Autolayout, view the module or check out the examples folder.
|
||||
|
||||
use objc_id::ShareId;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use objc_id::{Id, ShareId};
|
||||
use objc::runtime::{Class, Object};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
|
@ -84,7 +87,7 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
|||
#[derive(Debug)]
|
||||
pub struct ListViewRow<T = ()> {
|
||||
/// A pointer to the Objective-C runtime view controller.
|
||||
pub objc: ShareId<Object>,
|
||||
pub objc: Rc<RefCell<Id<Object>>>,
|
||||
|
||||
/// A pointer to the delegate for this view.
|
||||
pub delegate: Option<Box<T>>,
|
||||
|
@ -135,7 +138,7 @@ impl ListViewRow {
|
|||
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
|
||||
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
|
||||
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
|
||||
objc: unsafe { ShareId::from_ptr(view) },
|
||||
objc: Rc::new(RefCell::new(unsafe { Id::from_ptr(view) })),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +173,7 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
|
|||
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
|
||||
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
|
||||
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
|
||||
objc: unsafe { ShareId::from_ptr(view) },
|
||||
objc: Rc::new(RefCell::new(unsafe { Id::from_ptr(view) })),
|
||||
};
|
||||
|
||||
view
|
||||
|
@ -202,7 +205,7 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
|
|||
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
|
||||
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
|
||||
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
|
||||
objc: unsafe { ShareId::from_ptr(view) },
|
||||
objc: Rc::new(RefCell::new(unsafe { Id::from_ptr(view) })),
|
||||
};
|
||||
|
||||
(&mut delegate).did_load(view.clone_as_handle());
|
||||
|
@ -228,7 +231,7 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
|
|||
height: self.height.clone(),
|
||||
center_x: self.center_x.clone(),
|
||||
center_y: self.center_y.clone(),
|
||||
objc: self.objc.clone()
|
||||
objc: Rc::clone(&self.objc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -255,7 +258,7 @@ impl<T> ListViewRow<T> {
|
|||
height: self.height.clone(),
|
||||
center_x: self.center_x.clone(),
|
||||
center_y: self.center_y.clone(),
|
||||
objc: self.objc.clone()
|
||||
objc: Rc::clone(&self.objc)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,8 +266,9 @@ impl<T> ListViewRow<T> {
|
|||
pub fn set_identifier(&self, identifier: &'static str) {
|
||||
let identifier = NSString::new(identifier).into_inner();
|
||||
|
||||
let objc = self.objc.borrow();
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, setIdentifier:identifier];
|
||||
let _: () = msg_send![&**objc, setIdentifier:identifier];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,9 +276,10 @@ impl<T> ListViewRow<T> {
|
|||
pub fn set_background_color(&self, color: Color) {
|
||||
let bg = color.into_platform_specific_color();
|
||||
|
||||
let objc = self.objc.borrow();
|
||||
unsafe {
|
||||
let cg: id = msg_send![bg, CGColor];
|
||||
let layer: id = msg_send![&*self.objc, layer];
|
||||
let layer: id = msg_send![&**objc, layer];
|
||||
let _: () = msg_send![layer, setBackgroundColor:cg];
|
||||
}
|
||||
}
|
||||
|
@ -289,21 +294,29 @@ impl<T> ListViewRow<T> {
|
|||
x.into_inner()
|
||||
}).collect::<Vec<id>>().into();
|
||||
|
||||
let _: () = msg_send![&*self.objc, registerForDraggedTypes:types.into_inner()];
|
||||
let objc = self.objc.borrow();
|
||||
let _: () = msg_send![&**objc, registerForDraggedTypes:types.into_inner()];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for ListViewRow<T> {
|
||||
fn get_backing_node(&self) -> ShareId<Object> {
|
||||
self.objc.clone()
|
||||
let objc = self.objc.borrow();
|
||||
|
||||
unsafe {
|
||||
// @TODO: Need a better solution here.
|
||||
let x: id = msg_send![&**objc, self];
|
||||
ShareId::from_ptr(x)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_subview<V: Layout>(&self, view: &V) {
|
||||
let backing_node = view.get_backing_node();
|
||||
|
||||
let objc = self.objc.borrow();
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, addSubview:backing_node];
|
||||
let _: () = msg_send![&**objc, addSubview:backing_node];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
44
src/macos/enums.rs
Normal file
44
src/macos/enums.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
//! Generic enums that don't fit anywhere else yet.
|
||||
|
||||
use crate::foundation::{NSUInteger};
|
||||
|
||||
/// Used to set whether and/or how a view or cell draws a focus ring.
|
||||
#[cfg(feature = "macos")]
|
||||
#[derive(Debug)]
|
||||
pub enum FocusRingType {
|
||||
/// Whatever the default is.
|
||||
Default,
|
||||
|
||||
/// None.
|
||||
None,
|
||||
|
||||
/// Standard focus ring.
|
||||
Exterior,
|
||||
|
||||
// Should never be used, but used as a placeholder in case the underlying
|
||||
// system framework ever opts to return something we don't expect.
|
||||
Unknown(NSUInteger)
|
||||
}
|
||||
|
||||
#[cfg(feature = "macos")]
|
||||
impl From<FocusRingType> for NSUInteger {
|
||||
fn from(ring_type: FocusRingType) -> Self {
|
||||
match ring_type {
|
||||
FocusRingType::Default => 0,
|
||||
FocusRingType::None => 1,
|
||||
FocusRingType::Exterior => 2,
|
||||
FocusRingType::Unknown(i) => i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NSUInteger> for FocusRingType {
|
||||
fn from(i: NSUInteger) -> Self {
|
||||
match i {
|
||||
0 => Self::Default,
|
||||
1 => Self::None,
|
||||
2 => Self::Exterior,
|
||||
i => Self::Unknown(i)
|
||||
}
|
||||
}
|
||||
}
|
115
src/macos/event/mod.rs
Normal file
115
src/macos/event/mod.rs
Normal file
|
@ -0,0 +1,115 @@
|
|||
|
||||
use block::ConcreteBlock;
|
||||
|
||||
use objc::runtime::Object;
|
||||
use objc_id::Id;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
use crate::foundation::{id, nil, NSString};
|
||||
|
||||
/// An EventMask describes the type of event.
|
||||
#[derive(Debug)]
|
||||
pub enum EventMask {
|
||||
KeyDown
|
||||
}
|
||||
|
||||
/// A wrapper over an `NSEvent`.
|
||||
#[derive(Debug)]
|
||||
pub struct EventMonitor(pub Id<Object>);
|
||||
|
||||
/// A wrapper over an `NSEvent`.
|
||||
#[derive(Debug)]
|
||||
pub struct Event(pub Id<Object>);
|
||||
|
||||
impl Event {
|
||||
pub(crate) fn new(objc: id) -> Self {
|
||||
Event(unsafe {
|
||||
Id::from_ptr(objc)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn characters(&self) -> String {
|
||||
// @TODO: Check here if key event, invalid otherwise.
|
||||
// @TODO: Figure out if we can just return &str here, since the Objective-C side
|
||||
// should... make it work, I think.
|
||||
let characters = NSString::wrap(unsafe {
|
||||
msg_send![&*self.0, characters]
|
||||
});
|
||||
|
||||
characters.to_str().to_string()
|
||||
}
|
||||
|
||||
/*pub fn contains_modifier_flags(&self, flags: &[EventModifierFlag]) -> bool {
|
||||
let modifier_flags: NSUInteger = unsafe {
|
||||
msg_send![&*self.0, modifierFlags]
|
||||
};
|
||||
|
||||
for flag in flags {
|
||||
let f: NSUInteger = flag.into();
|
||||
|
||||
}
|
||||
|
||||
false
|
||||
}*/
|
||||
|
||||
/// Register an event handler with the system event stream. This method
|
||||
/// watches for events that occur _within the application_. Events outside
|
||||
/// of the application require installing a `monitor_global_events` handler.
|
||||
///
|
||||
/// Note that in order to monitor all possible events, both local and global
|
||||
/// monitors are required - the streams don't mix.
|
||||
pub fn local_monitor<F>(_mask: EventMask, handler: F) -> EventMonitor
|
||||
where
|
||||
F: Fn(Event) -> Option<Event> + Send + Sync + 'static
|
||||
{
|
||||
let block = ConcreteBlock::new(move |event: id| {
|
||||
let evt = Event::new(event);
|
||||
|
||||
match handler(evt) {
|
||||
Some(mut evt) => &mut *evt.0,
|
||||
None => nil
|
||||
}
|
||||
});
|
||||
let block = block.copy();
|
||||
|
||||
EventMonitor(unsafe {
|
||||
msg_send![class!(NSEvent), addLocalMonitorForEventsMatchingMask:1024
|
||||
handler:block]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
use crate::foundation::NSUInteger;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum EventModifierFlag {
|
||||
CapsLock,
|
||||
Control,
|
||||
Option,
|
||||
Command,
|
||||
DeviceIndependentFlagsMask
|
||||
}
|
||||
|
||||
impl From<EventModifierFlag> for NSUInteger {
|
||||
fn from(flag: EventModifierFlag) -> NSUInteger {
|
||||
match flag {
|
||||
EventModifierFlag::CapsLock => 1 << 16,
|
||||
EventModifierFlag::Control => 1 << 18,
|
||||
EventModifierFlag::Option => 1 << 19,
|
||||
EventModifierFlag::Command => 1 << 20,
|
||||
EventModifierFlag::DeviceIndependentFlagsMask => 0xffff0000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EventModifierFlag> for NSUInteger {
|
||||
fn from(flag: &EventModifierFlag) -> NSUInteger {
|
||||
match flag {
|
||||
EventModifierFlag::CapsLock => 1 << 16,
|
||||
EventModifierFlag::Control => 1 << 18,
|
||||
EventModifierFlag::Option => 1 << 19,
|
||||
EventModifierFlag::Command => 1 << 20,
|
||||
EventModifierFlag::DeviceIndependentFlagsMask => 0xffff0000
|
||||
}
|
||||
}
|
||||
}
|
|
@ -87,7 +87,7 @@ impl MenuItem {
|
|||
match self {
|
||||
MenuItem::Separator => MenuItem::Separator,
|
||||
|
||||
MenuItem::Entry((item, old_action)) => {
|
||||
MenuItem::Entry((item, _)) => {
|
||||
let action = TargetActionHandler::new(&*item, action);
|
||||
MenuItem::Entry((item, Some(action)))
|
||||
}
|
||||
|
|
|
@ -24,6 +24,12 @@ pub use app::*;
|
|||
mod cursor;
|
||||
pub use cursor::{Cursor, CursorType};
|
||||
|
||||
mod enums;
|
||||
pub use enums::{FocusRingType};
|
||||
|
||||
mod event;
|
||||
pub use event::*;
|
||||
|
||||
pub mod menu;
|
||||
pub mod printing;
|
||||
pub mod toolbar;
|
||||
|
|
|
@ -12,7 +12,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
|||
|
||||
use crate::foundation::{id, NSString};
|
||||
use crate::invoker::TargetActionHandler;
|
||||
use crate::button::Button;
|
||||
use crate::button::{Button, BezelStyle};
|
||||
use crate::image::Image;
|
||||
|
||||
/// Wraps `NSToolbarItem`. Enables configuring things like size, view, and so on.
|
||||
|
@ -57,7 +57,7 @@ impl ToolbarItem {
|
|||
|
||||
/// Sets and takes ownership of the button for this item.
|
||||
pub fn set_button(&mut self, button: Button) {
|
||||
button.set_bezel_style(11);
|
||||
button.set_bezel_style(BezelStyle::TexturedRounded);
|
||||
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, setView:&*button.objc];
|
||||
|
|
|
@ -217,7 +217,7 @@ impl<T> Label<T> {
|
|||
}
|
||||
|
||||
/// Call this to set the color of the text.
|
||||
pub fn set_color(&self, color: Color) {
|
||||
pub fn set_text_color(&self, color: Color) {
|
||||
let color = color.into_platform_specific_color();
|
||||
|
||||
unsafe {
|
||||
|
@ -234,6 +234,15 @@ impl<T> Label<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Retrieve the text currently held in the label.
|
||||
pub fn text(&self) -> String {
|
||||
let s = NSString::wrap(unsafe {
|
||||
msg_send![&*self.objc, stringValue]
|
||||
});
|
||||
|
||||
s.to_str().to_string()
|
||||
}
|
||||
|
||||
pub fn set_text_alignment(&self, alignment: TextAlign) {
|
||||
unsafe {
|
||||
let alignment: NSInteger = alignment.into();
|
||||
|
|
13
src/utils.rs
13
src/utils.rs
|
@ -24,27 +24,18 @@ pub mod os {
|
|||
/// runtime check thhat returns a boolean indicating whether the current version is a minimum target.
|
||||
#[inline(always)]
|
||||
pub fn is_minimum_version(minimum_major: u64) -> bool {
|
||||
//println!("Looking for: {}", major);
|
||||
//println!("VERSION: {}", OS_VERSION.version());
|
||||
|
||||
match OS_VERSION.version() {
|
||||
Version::Semantic(os_major, _, _) => { *os_major >= minimum_major },
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// In rare cases we need to check whether something is a specific version of macOS. This is a
|
||||
/// runtime check thhat returns a boolean indicating whether the current version is a minimum target.
|
||||
#[inline(always)]
|
||||
pub fn is_minimum_semversion(major: u64, minor: u64, patch: u64) -> bool {
|
||||
match OS_VERSION.version() {
|
||||
Version::Semantic(maj, min, p) => {
|
||||
major >= *maj && minor >= *min && patch >= *p
|
||||
},
|
||||
|
||||
_ => { false }
|
||||
}
|
||||
let target = Version::Semantic(major, minor, patch);
|
||||
OS_VERSION.version() > &target
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,12 +11,12 @@ use std::sync::Once;
|
|||
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{Class, Object, Sel, BOOL};
|
||||
use objc::{class, sel, sel_impl};
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc_id::Id;
|
||||
|
||||
use crate::foundation::{load_or_register_class, id, YES, NO, NSUInteger};
|
||||
use crate::foundation::{load_or_register_class, id, nil, YES, NO, NSUInteger};
|
||||
use crate::dragdrop::DragInfo;
|
||||
use crate::view::{VIEW_DELEGATE_PTR, ViewDelegate};
|
||||
use crate::view::{VIEW_DELEGATE_PTR, BACKGROUND_COLOR, ViewDelegate};
|
||||
use crate::utils::load;
|
||||
|
||||
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
|
||||
|
@ -74,6 +74,19 @@ extern fn dragging_exited<T: ViewDelegate>(this: &mut Object, _: Sel, info: id)
|
|||
});
|
||||
}
|
||||
|
||||
/// Called for layer updates.
|
||||
extern fn update_layer(this: &Object, _: Sel) {
|
||||
unsafe {
|
||||
let background_color: id = *this.get_ivar(BACKGROUND_COLOR);
|
||||
|
||||
if background_color != nil {
|
||||
let layer: id = msg_send![this, layer];
|
||||
let cg: id = msg_send![background_color, CGColor];
|
||||
let _: () = msg_send![layer, setBackgroundColor:cg];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Injects an `NSView` subclass. This is used for the default views that don't use delegates - we
|
||||
/// have separate classes here since we don't want to waste cycles on methods that will never be
|
||||
/// used if there's no delegates.
|
||||
|
@ -86,6 +99,10 @@ pub(crate) fn register_view_class() -> *const Class {
|
|||
let mut decl = ClassDecl::new("RSTView", superclass).unwrap();
|
||||
|
||||
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
|
||||
decl.add_method(sel!(updateLayer), update_layer as extern fn(&Object, _));
|
||||
decl.add_method(sel!(wantsUpdateLayer), enforce_normalcy as extern fn(&Object, _) -> BOOL);
|
||||
|
||||
decl.add_ivar::<id>(BACKGROUND_COLOR);
|
||||
|
||||
VIEW_CLASS = decl.register();
|
||||
});
|
||||
|
@ -100,7 +117,18 @@ pub(crate) fn register_view_class_with_delegate<T: ViewDelegate>(instance: &T) -
|
|||
// A pointer to the ViewDelegate instance on the Rust side.
|
||||
// It's expected that this doesn't move.
|
||||
decl.add_ivar::<usize>(VIEW_DELEGATE_PTR);
|
||||
decl.add_ivar::<id>(BACKGROUND_COLOR);
|
||||
|
||||
decl.add_method(
|
||||
sel!(updateLayer),
|
||||
update_layer as extern fn(&Object, _)
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(wantsUpdateLayer),
|
||||
enforce_normalcy as extern fn(&Object, _) -> BOOL
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(isFlipped),
|
||||
enforce_normalcy as extern fn(&Object, _) -> BOOL
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
//!
|
||||
//! For more information on Autolayout, view the module or check out the examples folder.
|
||||
|
||||
use objc_id::ShareId;
|
||||
use objc_id::{Id, ShareId};
|
||||
use objc::runtime::{Class, Object};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
|
@ -50,6 +50,9 @@ use crate::color::Color;
|
|||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||
use crate::pasteboard::PasteboardType;
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
|
||||
|
@ -68,6 +71,7 @@ pub use controller::ViewController;
|
|||
mod traits;
|
||||
pub use traits::ViewDelegate;
|
||||
|
||||
pub(crate) static BACKGROUND_COLOR: &str = "alchemyBackgroundColor";
|
||||
pub(crate) static VIEW_DELEGATE_PTR: &str = "rstViewDelegatePtr";
|
||||
|
||||
/// A helper method for instantiating view classes and applying default settings to them.
|
||||
|
@ -89,7 +93,7 @@ fn common_init(class: *const Class) -> id {
|
|||
#[derive(Debug)]
|
||||
pub struct View<T = ()> {
|
||||
/// A pointer to the Objective-C runtime view controller.
|
||||
pub objc: ShareId<Object>,
|
||||
pub objc: Rc<RefCell<Id<Object>>>,
|
||||
|
||||
/// A pointer to the delegate for this view.
|
||||
pub delegate: Option<Box<T>>,
|
||||
|
@ -140,7 +144,7 @@ impl View {
|
|||
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
|
||||
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
|
||||
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
|
||||
objc: unsafe { ShareId::from_ptr(view) },
|
||||
objc: Rc::new(RefCell::new(unsafe { Id::from_ptr(view) })),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +174,7 @@ impl<T> View<T> where T: ViewDelegate + 'static {
|
|||
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
|
||||
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
|
||||
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
|
||||
objc: unsafe { ShareId::from_ptr(view) },
|
||||
objc: Rc::new(RefCell::new(unsafe { Id::from_ptr(view) })),
|
||||
};
|
||||
|
||||
(&mut delegate).did_load(view.clone_as_handle());
|
||||
|
@ -195,19 +199,24 @@ impl<T> View<T> {
|
|||
height: self.height.clone(),
|
||||
center_x: self.center_x.clone(),
|
||||
center_y: self.center_y.clone(),
|
||||
objc: self.objc.clone()
|
||||
objc: Rc::clone(&self.objc) //.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Call this to set the background color for the backing layer.
|
||||
pub fn set_background_color(&self, color: Color) {
|
||||
let bg = color.into_platform_specific_color();
|
||||
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
|
||||
let mut objc = self.objc.borrow_mut();
|
||||
|
||||
unsafe {
|
||||
(&mut **objc).set_ivar(BACKGROUND_COLOR, color.as_ref().to_objc());
|
||||
}
|
||||
/*let bg = color.as_ref().into_platform_specific_color();
|
||||
|
||||
unsafe {
|
||||
let cg: id = msg_send![bg, CGColor];
|
||||
let layer: id = msg_send![&*self.objc, layer];
|
||||
let _: () = msg_send![layer, setBackgroundColor:cg];
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
/// Register this view for drag and drop operations.
|
||||
|
@ -220,21 +229,28 @@ impl<T> View<T> {
|
|||
x.into_inner()
|
||||
}).collect::<Vec<id>>().into();
|
||||
|
||||
let _: () = msg_send![&*self.objc, registerForDraggedTypes:types.into_inner()];
|
||||
let objc = self.objc.borrow();
|
||||
let _: () = msg_send![&**objc, registerForDraggedTypes:types.into_inner()];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for View<T> {
|
||||
fn get_backing_node(&self) -> ShareId<Object> {
|
||||
self.objc.clone()
|
||||
let objc = self.objc.borrow();
|
||||
|
||||
unsafe {
|
||||
let x: id = msg_send![&**objc, self];
|
||||
ShareId::from_ptr(x)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_subview<V: Layout>(&self, view: &V) {
|
||||
let backing_node = view.get_backing_node();
|
||||
|
||||
let objc = self.objc.borrow();
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, addSubview:backing_node];
|
||||
let _: () = msg_send![&**objc, addSubview:backing_node];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,13 +263,13 @@ impl<T> Drop for View<T> {
|
|||
///
|
||||
/// There are, thankfully, no delegates we need to break here.
|
||||
fn drop(&mut self) {
|
||||
if self.delegate.is_some() {
|
||||
/*if self.delegate.is_some() {
|
||||
unsafe {
|
||||
let superview: id = msg_send![&*self.objc, superview];
|
||||
if superview != nil {
|
||||
let _: () = msg_send![&*self.objc, removeFromSuperview];
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,4 +48,6 @@ pub trait ViewDelegate {
|
|||
/// Invoked when the dragged image exits the destination’s bounds rectangle (in the case of a view) or its frame
|
||||
/// rectangle (in the case of a window object).
|
||||
fn dragging_exited(&self, info: DragInfo) {}
|
||||
|
||||
//fn perform_key_equivalent(&self, event: Event) -> bool { false }
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue