Provide a standard NSMenu generator, fix a bug in NSEvent key character releasing, and add an animation example.
This commit is contained in:
parent
b9bf30e6f1
commit
f925205876
179
examples/animation.rs
Normal file
179
examples/animation.rs
Normal file
|
@ -0,0 +1,179 @@
|
|||
//! This example builds on the AutoLayout example, but adds in animation
|
||||
//! via `AnimationContext`. Views and layout anchors have special proxy objects that can be cloned
|
||||
//! into handlers, enabling basic animation support within `AnimationContext`.
|
||||
//!
|
||||
//! This one is a bit kludgier than some other examples, but the comments throughout this should
|
||||
//! clarify why that is.
|
||||
|
||||
use cacao::color::Color;
|
||||
use cacao::layout::{Layout, LayoutConstraint, LayoutConstraintAnimatorProxy};
|
||||
use cacao::view::{View, ViewAnimatorProxy};
|
||||
|
||||
use cacao::appkit::{App, AppDelegate, AnimationContext};
|
||||
use cacao::appkit::{Event, EventMask, EventMonitor};
|
||||
use cacao::appkit::menu::Menu;
|
||||
use cacao::appkit::window::{Window, WindowConfig, WindowDelegate};
|
||||
|
||||
struct BasicApp {
|
||||
window: Window<AppWindow>
|
||||
}
|
||||
|
||||
impl AppDelegate for BasicApp {
|
||||
fn did_finish_launching(&self) {
|
||||
App::set_menu(Menu::standard());
|
||||
App::activate();
|
||||
|
||||
self.window.show();
|
||||
}
|
||||
|
||||
fn should_terminate_after_last_window_closed(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// This map is the four different animation frames that we display, per view type.
|
||||
/// Why do we have this here?
|
||||
///
|
||||
/// Well, it's because there's no random number generator in the standard library, and I really
|
||||
/// dislike when examples need crates attached to 'em.
|
||||
///
|
||||
/// The basic mapping logic is this: each entry is a view's frame(s), and each frame is an array
|
||||
/// of:
|
||||
///
|
||||
/// [top, left, width, height, alpha]
|
||||
///
|
||||
/// We then treat each frame index as follows:
|
||||
///
|
||||
/// w: 0
|
||||
/// a: 1
|
||||
/// s: 2
|
||||
/// d: 3
|
||||
const ANIMATIONS: [[[f64; 5]; 4]; 3] = [
|
||||
// Blue
|
||||
[
|
||||
[44., 16., 100., 100., 1.],
|
||||
[128., 84., 144., 124., 1.],
|
||||
[32., 32., 44., 44., 0.7],
|
||||
[328., 157., 200., 200., 0.7],
|
||||
],
|
||||
|
||||
// Red
|
||||
[
|
||||
[44., 132., 100., 100., 1.],
|
||||
[40., 47., 80., 64., 0.7],
|
||||
[84., 220., 600., 109., 1.0],
|
||||
[48., 600., 340., 44., 0.7],
|
||||
],
|
||||
|
||||
// Green
|
||||
[
|
||||
[44., 248., 100., 100., 1.],
|
||||
[420., 232., 420., 244., 0.7],
|
||||
[310., 440., 150., 238., 0.7],
|
||||
[32., 32., 44., 44., 1.],
|
||||
]
|
||||
];
|
||||
|
||||
/// A helper method for generating frame constraints that we want to be animating.
|
||||
fn apply_styles(
|
||||
view: &View,
|
||||
parent: &View,
|
||||
background_color: Color,
|
||||
animation_table_index: usize
|
||||
) -> [LayoutConstraint; 4] {
|
||||
view.set_background_color(background_color);
|
||||
view.layer.set_corner_radius(16.);
|
||||
parent.add_subview(view);
|
||||
|
||||
let animation = ANIMATIONS[animation_table_index][0];
|
||||
|
||||
[
|
||||
view.top.constraint_equal_to(&parent.top).offset(animation[0]),
|
||||
view.left.constraint_equal_to(&parent.left).offset(animation[1]),
|
||||
view.width.constraint_equal_to_constant(animation[2]),
|
||||
view.height.constraint_equal_to_constant(animation[3])
|
||||
]
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct AppWindow {
|
||||
content: View,
|
||||
blue: View,
|
||||
red: View,
|
||||
green: View,
|
||||
key_monitor: Option<EventMonitor>
|
||||
}
|
||||
|
||||
impl WindowDelegate for AppWindow {
|
||||
const NAME: &'static str = "WindowDelegate";
|
||||
|
||||
fn did_load(&mut self, window: Window) {
|
||||
window.set_title("Animation Example (Use W/A/S/D to change state!)");
|
||||
window.set_minimum_content_size(300., 300.);
|
||||
|
||||
window.set_content_view(&self.content);
|
||||
|
||||
let blue_frame = apply_styles(&self.blue, &self.content, Color::SystemBlue, 0);
|
||||
let red_frame = apply_styles(&self.red, &self.content, Color::SystemRed, 1);
|
||||
let green_frame = apply_styles(&self.green, &self.content, Color::SystemGreen, 2);
|
||||
|
||||
let alpha_animators = [&self.blue, &self.red, &self.green].iter().map(|view| {
|
||||
view.animator.clone()
|
||||
}).collect::<Vec<ViewAnimatorProxy>>();
|
||||
|
||||
let constraint_animators = [blue_frame, red_frame, green_frame].iter().map(|frame| {
|
||||
LayoutConstraint::activate(frame);
|
||||
|
||||
vec![
|
||||
frame[0].animator.clone(),
|
||||
frame[1].animator.clone(),
|
||||
frame[2].animator.clone(),
|
||||
frame[3].animator.clone(),
|
||||
]
|
||||
}).collect::<Vec<Vec<LayoutConstraintAnimatorProxy>>>();
|
||||
|
||||
// Monitor key change events for w/a/s/d, and then animate each view to their correct
|
||||
// frame and alpha value.
|
||||
self.key_monitor = Some(Event::local_monitor(EventMask::KeyDown, move |evt| {
|
||||
let characters = evt.characters();
|
||||
|
||||
let animation_index = match characters.as_ref() {
|
||||
"w" => 0,
|
||||
"a" => 1,
|
||||
"s" => 2,
|
||||
"d" => 3,
|
||||
_ => 4
|
||||
};
|
||||
|
||||
if animation_index == 4 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let alpha_animators = alpha_animators.clone();
|
||||
let constraint_animators = constraint_animators.clone();
|
||||
|
||||
AnimationContext::run(move |_ctx| {
|
||||
alpha_animators.iter().enumerate().for_each(move |(index, view)| {
|
||||
let animation = ANIMATIONS[index][animation_index];
|
||||
view.set_alpha(animation[4]);
|
||||
});
|
||||
|
||||
constraint_animators.iter().enumerate().for_each(move |(index, frame)| {
|
||||
let animation = ANIMATIONS[index][animation_index];
|
||||
frame[0].set_offset(animation[0]);
|
||||
frame[1].set_offset(animation[1]);
|
||||
frame[2].set_offset(animation[2]);
|
||||
frame[3].set_offset(animation[3]);
|
||||
});
|
||||
});
|
||||
|
||||
None
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new("com.test.window", BasicApp {
|
||||
window: Window::with(WindowConfig::default(), AppWindow::default())
|
||||
}).run();
|
||||
}
|
|
@ -32,7 +32,7 @@ impl Event {
|
|||
// @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::from_retained(unsafe {
|
||||
let characters = NSString::retain(unsafe {
|
||||
msg_send![&*self.0, characters]
|
||||
});
|
||||
|
||||
|
|
|
@ -70,4 +70,46 @@ impl Menu {
|
|||
|
||||
menu
|
||||
}
|
||||
|
||||
/// Convenience method for the bare-minimum NSMenu structure that "just works" for all
|
||||
/// applications, as expected.
|
||||
pub fn standard() -> Vec<Menu> {
|
||||
vec![
|
||||
Menu::new("", vec![
|
||||
MenuItem::Services,
|
||||
MenuItem::Separator,
|
||||
MenuItem::Hide,
|
||||
MenuItem::HideOthers,
|
||||
MenuItem::ShowAll,
|
||||
MenuItem::Separator,
|
||||
MenuItem::Quit
|
||||
]),
|
||||
|
||||
Menu::new("File", vec![
|
||||
MenuItem::CloseWindow
|
||||
]),
|
||||
|
||||
Menu::new("Edit", vec![
|
||||
MenuItem::Undo,
|
||||
MenuItem::Redo,
|
||||
MenuItem::Separator,
|
||||
MenuItem::Cut,
|
||||
MenuItem::Copy,
|
||||
MenuItem::Paste,
|
||||
MenuItem::Separator,
|
||||
MenuItem::SelectAll
|
||||
]),
|
||||
|
||||
Menu::new("View", vec![
|
||||
MenuItem::EnterFullScreen
|
||||
]),
|
||||
|
||||
Menu::new("Window", vec![
|
||||
MenuItem::Minimize,
|
||||
MenuItem::Zoom,
|
||||
MenuItem::Separator,
|
||||
MenuItem::new("Bring All to Front")
|
||||
])
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue