Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: WIP Custom menu builder #1921

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions examples/custom_menu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
menu::{Menu, MenuItem},
window::WindowBuilder,
};

pub enum Message {
MenuClickAddNewItem,
}

fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();

let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_menu(vec![
Menu::new(
"",
vec![
MenuItem::About("Todos".to_string()),
MenuItem::Separator,
MenuItem::new("preferences".to_string(), "Preferences".to_string()).key(","),
MenuItem::Separator,
MenuItem::Services,
MenuItem::Separator,
MenuItem::Hide,
MenuItem::HideOthers,
MenuItem::ShowAll,
MenuItem::Separator,
MenuItem::Quit,
],
),
Menu::new(
"Custom menu",
vec![
MenuItem::new("add_todo".to_string(), "Add Todo".to_string()).key("+"),
MenuItem::Separator,
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]),
Menu::new("Help", vec![]),
])
.build(&event_loop)
.unwrap();

event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;

match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
Event::MainEventsCleared => {
window.request_redraw();
}
Event::MenuEvent(menu_id) => {
println!("Clicked on {}", menu_id);
window.set_title("New window title!");
}
_ => (),
}
});
}
64 changes: 64 additions & 0 deletions examples/custom_menu_multiwindow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::collections::HashMap;

use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
menu::{Menu, MenuItem},
window::{Window, WindowBuilder},
};

fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();

let mut windows = HashMap::new();
for _ in 0..3 {
let window = WindowBuilder::new()
.with_menu(vec![Menu::new(
"asf",
vec![
MenuItem::About("My app!".to_string()),
MenuItem::Separator,
MenuItem::ShowAll,
],
)])
.build(&event_loop)
.unwrap();
windows.insert(window.id(), window);
}

event_loop.run(move |event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;

match event {
Event::WindowEvent { event, window_id } => {
match event {
WindowEvent::CloseRequested => {
println!("Window {:?} has received the signal to close", window_id);

// This drops the window, causing it to close.
windows.remove(&window_id);

if windows.is_empty() {
*control_flow = ControlFlow::Exit;
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
..
},
..
} => {
let window = Window::new(&event_loop).unwrap();
windows.insert(window.id(), window);
}
_ => (),
}
}
_ => (),
}
})
}
6 changes: 6 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ pub enum Event<'a, T: 'static> {
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event)
UserEvent(T),

/// Emitted when a menu has been clicked.
MenuEvent(String),

/// Emitted when the application has been suspended.
Suspended,

Expand Down Expand Up @@ -138,6 +141,7 @@ impl<T: Clone> Clone for Event<'static, T> {
LoopDestroyed => LoopDestroyed,
Suspended => Suspended,
Resumed => Resumed,
MenuEvent(event) => MenuEvent(event.clone()),
}
}
}
Expand All @@ -156,6 +160,7 @@ impl<'a, T> Event<'a, T> {
LoopDestroyed => Ok(LoopDestroyed),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
MenuEvent(event) => Ok(MenuEvent(event)),
}
}

Expand All @@ -176,6 +181,7 @@ impl<'a, T> Event<'a, T> {
LoopDestroyed => Some(LoopDestroyed),
Suspended => Some(Suspended),
Resumed => Some(Resumed),
MenuEvent(event) => Some(MenuEvent(event)),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ pub mod error;
pub mod event;
pub mod event_loop;
mod icon;
pub mod menu;
pub mod monitor;
mod platform_impl;
pub mod window;
Expand Down
104 changes: 104 additions & 0 deletions src/menu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#[derive(Debug, Clone)]
pub struct Menu {
pub title: String,
pub items: Vec<MenuItem>,
}

impl Menu {
pub fn new(title: &str, items: Vec<MenuItem>) -> Self {
Self {
title: String::from(title),
items,
}
}
}

#[derive(Debug, Clone)]
pub struct CustomMenu {
pub id: String,
pub name: String,
pub key: Option<String>,
}

#[derive(Debug, Clone)]
pub enum MenuItem {
/// A custom MenuItem. This type functions as a builder, so you can customize it easier.
/// You can (and should) create this variant via the `new(title)` method, but if you need to do
/// something crazier, then wrap it in this and you can hook into the Cacao menu system
/// accordingly.
Custom(CustomMenu),

/// Shows a standard "About" item, which will bring up the necessary window when clicked
/// (include a `credits.html` in your App to make use of here). The argument baked in here
/// should be your app name.
About(String),

/// A standard "hide the app" menu item.
Hide,

/// A standard "Services" menu item.
Services,

/// A "hide all other windows" menu item.
HideOthers,

/// A menu item to show all the windows for this app.
ShowAll,

/// Close the current window.
CloseWindow,

/// A "quit this app" menu icon.
Quit,

/// A menu item for enabling copying (often text) from responders.
Copy,

/// A menu item for enabling cutting (often text) from responders.
Cut,

/// An "undo" menu item; particularly useful for supporting the cut/copy/paste/undo lifecycle
/// of events.
Undo,

/// An "redo" menu item; particularly useful for supporting the cut/copy/paste/undo lifecycle
/// of events.
Redo,

/// A menu item for selecting all (often text) from responders.
SelectAll,

/// A menu item for pasting (often text) into responders.
Paste,

/// A standard "enter full screen" item.
EnterFullScreen,

/// An item for minimizing the window with the standard system controls.
Minimize,

/// An item for instructing the app to zoom. Your app must react to this with necessary window
/// lifecycle events.
Zoom,

/// Represents a Separator. It's useful nonetheless for
/// separating out pieces of the `NSMenu` structure.
Separator,
}

impl MenuItem {
pub fn new(unique_menu_id: String, title: String) -> Self {
MenuItem::Custom(CustomMenu {
id: unique_menu_id,
key: None,
name: title,
})
}

pub fn key(mut self, key: &str) -> Self {
if let MenuItem::Custom(ref mut custom_menu) = self {
custom_menu.key = Some(key.to_string());
}
self
}
}
4 changes: 0 additions & 4 deletions src/platform_impl/macos/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ use crate::{
platform_impl::platform::{
event::{EventProxy, EventWrapper},
event_loop::{post_dummy_event, PanicInfo},
menu,
observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker},
util::{IdRef, Never},
window::get_window_id,
Expand Down Expand Up @@ -275,9 +274,6 @@ impl AppState {
pub fn launched() {
HANDLER.set_ready();
HANDLER.waker().start();
// The menubar initialization should be before the `NewEvents` event, to allow overriding
// of the default menu in the event
menu::initialize();
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init,
Expand Down
Loading