Skip to content
Draft
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
560 changes: 443 additions & 117 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ categories = ["gui"]
rust-version = "1.71"

[features]
default = ["libxdo"]
libxdo = ["dep:libxdo"]
default = []
common-controls-v6 = []
serde = ["dep:serde", "dpi/serde"]

Expand Down Expand Up @@ -42,8 +41,8 @@ features = [
]

[target.'cfg(target_os = "linux")'.dependencies]
gtk = "0.18"
libxdo = { version = "0.6.0", optional = true }
gtk4 = "0.9"
png = "0.17"

[target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6.0"
Expand Down
96 changes: 96 additions & 0 deletions examples/gtk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#[cfg(target_os = "linux")]
use gtk4::prelude::*;
#[cfg(target_os = "linux")]
use keyboard_types::{Code, Modifiers};
#[cfg(target_os = "linux")]
use muda::{accelerator::Accelerator, MenuEvent};

#[cfg(target_os = "linux")]
fn main() {
// Create a new application
let application = gtk4::Application::builder()
.application_id("com.github.gtk4-rs.examples.menubar")
.build();
application.connect_startup(on_startup);
application.connect_activate(on_activate);
application.run();
}

#[cfg(target_os = "linux")]
fn on_startup(_: &gtk4::Application) {
MenuEvent::set_event_handler(Some(|event| {
println!("{event:?}");
}));
}

#[cfg(target_os = "linux")]
fn on_activate(application: &gtk4::Application) {
use muda::ContextMenu;

let window = gtk4::ApplicationWindow::builder()
.application(application)
.title("Menubar Example")
.default_width(350)
.default_height(350)
.show_menubar(true)
.build();

window.present();

let about_menu_item = muda::MenuItem::new("About", true, None);

let check = muda::CheckMenuItem::new(
"Check",
true,
true,
Some(Accelerator::new(Modifiers::empty(), Code::KeyQ)),
);

let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
let icon = load_icon(std::path::Path::new(path));
let icon_menu_item = muda::IconMenuItem::new("Icon", true, Some(icon), None);

let quit_menu_item = muda::MenuItem::with_id(
"quit",
"&Quit",
true,
Some(Accelerator::new(Modifiers::CONTROL, Code::KeyQ)),
);

let file_menu = muda::Submenu::new("&File", true);
file_menu.append(&about_menu_item).unwrap();
file_menu.append(&check).unwrap();
file_menu.append(&icon_menu_item).unwrap();
file_menu.append(&quit_menu_item).unwrap();

let menubar = muda::Menu::new();
menubar.append(&file_menu).unwrap();

let vbox = gtk4::Box::new(gtk4::Orientation::Vertical, 0);
menubar.init_for_gtk_window(&window, Some(&vbox)).unwrap();

let btn = gtk4::Button::with_label("ASdasd");
let w = window.clone();

btn.connect_clicked(move |_| {
file_menu.show_context_menu_for_gtk_window(w.dynamic_cast_ref().unwrap(), None);
});
vbox.append(&btn);

window.set_child(Some(&vbox));
}

#[cfg(not(target_os = "linux"))]
fn main() {}

fn load_icon(path: &std::path::Path) -> muda::Icon {
let (icon_rgba, icon_width, icon_height) = {
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
(rgba, width, height)
};
muda::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
}
20 changes: 10 additions & 10 deletions examples/tao.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ fn main() {
"custom-i-1",
"C&ustom 1",
true,
Some(Accelerator::new(Some(Modifiers::ALT), Code::KeyC)),
Some(Accelerator::new(Modifiers::ALT, Code::KeyC)),
);

let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
Expand All @@ -98,7 +98,7 @@ fn main() {
"Image custom 1",
true,
Some(icon),
Some(Accelerator::new(Some(Modifiers::CONTROL), Code::KeyC)),
Some(Accelerator::new(Modifiers::CONTROL, Code::KeyC)),
);

let check_custom_i_1 =
Expand All @@ -110,7 +110,7 @@ fn main() {
"Check Custom 3",
true,
true,
Some(Accelerator::new(Some(Modifiers::SHIFT), Code::KeyD)),
Some(Accelerator::new(Modifiers::SHIFT, Code::KeyD)),
);

let copy_i = PredefinedMenuItem::copy(None);
Expand Down Expand Up @@ -153,11 +153,11 @@ fn main() {
menu_bar.init_for_hwnd(window.hwnd() as _);
menu_bar.init_for_hwnd(window2.hwnd() as _);
}
#[cfg(target_os = "linux")]
{
menu_bar.init_for_gtk_window(window.gtk_window(), window.default_vbox());
menu_bar.init_for_gtk_window(window2.gtk_window(), window2.default_vbox());
}
// #[cfg(target_os = "linux")]
// {
// menu_bar.init_for_gtk_window(window.gtk_window(), window.default_vbox());
// menu_bar.init_for_gtk_window(window2.gtk_window(), window2.default_vbox());
// }
#[cfg(target_os = "macos")]
{
menu_bar.init_for_nsapp();
Expand Down Expand Up @@ -230,8 +230,8 @@ fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option<P
unsafe {
menu.show_context_menu_for_hwnd(window.hwnd() as _, position);
}
#[cfg(target_os = "linux")]
menu.show_context_menu_for_gtk_window(window.gtk_window().as_ref(), position);
// #[cfg(target_os = "linux")]
// menu.show_context_menu_for_gtk_window(window.gtk_window().as_ref(), position);
#[cfg(target_os = "macos")]
unsafe {
menu.show_context_menu_for_nsview(window.ns_view() as _, position);
Expand Down
4 changes: 2 additions & 2 deletions examples/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ impl AppMenu {
let custom_i_1 = MenuItem::new(
"C&ustom 1",
true,
Some(Accelerator::new(Some(Modifiers::ALT), Code::KeyC)),
Some(Accelerator::new(Modifiers::ALT, Code::KeyC)),
);

let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
Expand All @@ -208,7 +208,7 @@ impl AppMenu {
"Check Custom 3",
true,
true,
Some(Accelerator::new(Some(Modifiers::SHIFT), Code::KeyD)),
Some(Accelerator::new(Modifiers::SHIFT, Code::KeyD)),
);

let copy_i = PredefinedMenuItem::copy(None);
Expand Down
38 changes: 19 additions & 19 deletions examples/wry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ fn main() -> wry::Result<()> {
let custom_i_1 = MenuItem::new(
"C&ustom 1",
true,
Some(Accelerator::new(Some(Modifiers::ALT), Code::KeyC)),
Some(Accelerator::new(Modifiers::ALT, Code::KeyC)),
);

let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
Expand All @@ -106,7 +106,7 @@ fn main() -> wry::Result<()> {
"Image custom 1",
true,
Some(icon),
Some(Accelerator::new(Some(Modifiers::CONTROL), Code::KeyC)),
Some(Accelerator::new(Modifiers::CONTROL, Code::KeyC)),
);

let check_custom_i_1 = CheckMenuItem::new("Check Custom 1", true, true, None);
Expand All @@ -115,7 +115,7 @@ fn main() -> wry::Result<()> {
"Check Custom 3",
true,
true,
Some(Accelerator::new(Some(Modifiers::SHIFT), Code::KeyD)),
Some(Accelerator::new(Modifiers::SHIFT, Code::KeyD)),
);

let copy_i = PredefinedMenuItem::copy(None);
Expand Down Expand Up @@ -172,12 +172,12 @@ fn main() -> wry::Result<()> {
}
#[cfg(target_os = "linux")]
{
menu_bar
.init_for_gtk_window(window.gtk_window(), window.default_vbox())
.unwrap();
menu_bar
.init_for_gtk_window(window2.gtk_window(), window2.default_vbox())
.unwrap();
// menu_bar
// .init_for_gtk_window(window.gtk_window(), window.default_vbox())
// .unwrap();
// menu_bar
// .init_for_gtk_window(window2.gtk_window(), window2.default_vbox())
// .unwrap();
}
#[cfg(target_os = "macos")]
{
Expand Down Expand Up @@ -257,14 +257,14 @@ fn main() -> wry::Result<()> {
.map(|(x, y)| (x.parse::<i32>().unwrap(), y.parse::<i32>().unwrap()))
.unwrap();

#[cfg(target_os = "linux")]
if let Some(menu_bar) = menu_bar
.clone()
.gtk_menubar_for_gtk_window(window.gtk_window())
{
use gtk::prelude::*;
y += menu_bar.allocated_height();
}
// #[cfg(target_os = "linux")]
// if let Some(menu_bar) = menu_bar
// .clone()
// .gtk_menubar_for_gtk_window(window.gtk_window())
// {
// use gtk::prelude::*;
// y += menu_bar.allocated_height();
// }

show_context_menu(&window, &file_m_c, Some(Position::Logical((x, y).into())))
}
Expand Down Expand Up @@ -315,8 +315,8 @@ fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option<P
unsafe {
menu.show_context_menu_for_hwnd(window.hwnd() as _, position);
}
#[cfg(target_os = "linux")]
menu.show_context_menu_for_gtk_window(window.gtk_window().as_ref(), position);
// #[cfg(target_os = "linux")]
// menu.show_context_menu_for_gtk_window(window.gtk_window().as_ref(), position);
#[cfg(target_os = "macos")]
unsafe {
menu.show_context_menu_for_nsview(window.ns_view() as _, position);
Expand Down
14 changes: 9 additions & 5 deletions src/accelerator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ pub struct Accelerator {

impl Accelerator {
/// Creates a new accelerator to define keyboard shortcuts throughout your application.
/// Only [`Modifiers::ALT`], [`Modifiers::SHIFT`], [`Modifiers::CONTROL`], and [`Modifiers::SUPER`]
pub fn new(mods: Option<Modifiers>, key: Code) -> Self {
let mut mods = mods.unwrap_or_else(Modifiers::empty);
/// Only [`Modifiers::ALT`], [`Modifiers::SHIFT`], [`Modifiers::CONTROL`], and [`Modifiers::SUPER`] are supported.
pub fn new(mut mods: Modifiers, key: Code) -> Self {
if mods.contains(Modifiers::META) {
mods.remove(Modifiers::META);
mods.insert(Modifiers::SUPER);
Expand All @@ -71,6 +70,11 @@ impl Accelerator {
Self { mods, key, id }
}

/// Same as [`Accelerator::new`] but consists of key without a modifier.
pub fn key_only(key: Code) -> Self {
Self::new(Modifiers::empty(), key)
}

fn generate_hash(mods: Modifiers, key: Code) -> u32 {
let mut accelerator_str = String::new();
if mods.contains(Modifiers::SHIFT) {
Expand Down Expand Up @@ -204,7 +208,7 @@ fn parse_accelerator(accelerator: &str) -> Result<Accelerator, AcceleratorParseE
}

let key = key.ok_or_else(|| AcceleratorParseError::InvalidFormat(accelerator.to_string()))?;
Ok(Accelerator::new(Some(mods), key))
Ok(Accelerator::new(mods, key))
}

fn parse_key(key: &str) -> Result<Code, AcceleratorParseError> {
Expand Down Expand Up @@ -423,7 +427,7 @@ fn test_parse_accelerator() {
fn test_equality() {
let h1 = parse_accelerator("Shift+KeyR").unwrap();
let h2 = parse_accelerator("Shift+KeyR").unwrap();
let h3 = Accelerator::new(Some(Modifiers::SHIFT), Code::KeyR);
let h3 = Accelerator::new(Modifiers::SHIFT, Code::KeyR);
let h4 = parse_accelerator("Alt+KeyR").unwrap();
let h5 = parse_accelerator("Alt+KeyR").unwrap();
let h6 = parse_accelerator("KeyR").unwrap();
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub enum Error {
AlreadyInitialized,
#[error(transparent)]
AcceleratorParseError(#[from] AcceleratorParseError),
#[error("Gtk Window doesn't have an application")]
GtkWindowWithoutApplication,
}

/// Convenient type alias of Result type for muda.
Expand Down
5 changes: 5 additions & 0 deletions src/icon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ pub enum BadIcon {
},
/// Produced when underlying OS functionality failed to create the icon
OsError(io::Error),
/// Produced when encoding provided RGBA into png
#[cfg(target_os = "linux")]
PngEncodingError(png::EncodingError),
}

impl fmt::Display for BadIcon {
Expand All @@ -53,6 +56,8 @@ impl fmt::Display for BadIcon {
width, height, pixel_count, width_x_height,
),
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e),
#[cfg(target_os = "linux")]
BadIcon::PngEncodingError(e) => write!(f, "PNG encoding error when instantiating the icon: {:?}", e),
}
}
}
Expand Down
21 changes: 14 additions & 7 deletions src/items/normal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ impl MenuItem {
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
pub fn new<S: AsRef<str>>(text: S, enabled: bool, accelerator: Option<Accelerator>) -> Self {
let item = crate::platform_impl::MenuChild::new(text.as_ref(), enabled, accelerator, None);
let item = crate::platform_impl::MenuChild::new_menu_item(
text.as_ref(),
enabled,
accelerator,
None,
);
Self {
id: Rc::new(item.id().clone()),
inner: Rc::new(RefCell::new(item)),
Expand All @@ -53,12 +58,14 @@ impl MenuItem {
let id = id.into();
Self {
id: Rc::new(id.clone()),
inner: Rc::new(RefCell::new(crate::platform_impl::MenuChild::new(
text.as_ref(),
enabled,
accelerator,
Some(id),
))),
inner: Rc::new(RefCell::new(
crate::platform_impl::MenuChild::new_menu_item(
text.as_ref(),
enabled,
accelerator,
Some(id),
),
)),
}
}

Expand Down
Loading