Skip to content

Commit

Permalink
feat!: gtk4
Browse files Browse the repository at this point in the history
closes #270

kicking off work on gtk4 migration

help is appreciated
  • Loading branch information
amrbashir committed Feb 1, 2025
1 parent d36d564 commit 3b5bbfe
Show file tree
Hide file tree
Showing 16 changed files with 782 additions and 1,720 deletions.
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.5.2"
Expand Down
65 changes: 65 additions & 0 deletions examples/gtk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#[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) {
let window = gtk4::ApplicationWindow::builder()
.application(application)
.title("Menubar Example")
.default_width(350)
.default_height(350)
.show_menubar(true)
.build();

window.present();

let menubar = {
// let file_menu = {
// let about_menu_item = muda::MenuItem::new("About", true, None);
// let quit_menu_item = muda::MenuItem::new(
// "About",
// 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(&quit_menu_item).unwrap();
// file_menu
// };

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

menubar
};

menubar
.init_for_gtk_window(&window, None::<&gtk4::Window>)
.unwrap();
}

#[cfg(not(target_os = "linux"))]
fn main() {}
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
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
37 changes: 14 additions & 23 deletions src/items/predefined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,43 +305,34 @@ impl PredefinedMenuItemType {

pub(crate) fn accelerator(&self) -> Option<Accelerator> {
match self {
PredefinedMenuItemType::Copy => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyC)),
PredefinedMenuItemType::Cut => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyX)),
PredefinedMenuItemType::Paste => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyV)),
PredefinedMenuItemType::Undo => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyZ)),
PredefinedMenuItemType::Copy => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyC)),
PredefinedMenuItemType::Cut => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyX)),
PredefinedMenuItemType::Paste => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyV)),
PredefinedMenuItemType::Undo => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyZ)),
#[cfg(target_os = "macos")]
PredefinedMenuItemType::Redo => Some(Accelerator::new(
Some(CMD_OR_CTRL | Modifiers::SHIFT),
Code::KeyZ,
)),
#[cfg(not(target_os = "macos"))]
PredefinedMenuItemType::Redo => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyY)),
PredefinedMenuItemType::SelectAll => {
Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyA))
}
PredefinedMenuItemType::Minimize => {
Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyM))
}
PredefinedMenuItemType::Redo => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyY)),
PredefinedMenuItemType::SelectAll => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyA)),
PredefinedMenuItemType::Minimize => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyM)),
#[cfg(target_os = "macos")]
PredefinedMenuItemType::Fullscreen => Some(Accelerator::new(
Some(Modifiers::META | Modifiers::CONTROL),
Code::KeyF,
)),
PredefinedMenuItemType::Hide => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyH)),
PredefinedMenuItemType::HideOthers => Some(Accelerator::new(
Some(CMD_OR_CTRL | Modifiers::ALT),
Code::KeyH,
)),
#[cfg(target_os = "macos")]
PredefinedMenuItemType::CloseWindow => {
Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyW))
PredefinedMenuItemType::Hide => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyH)),
PredefinedMenuItemType::HideOthers => {
Some(Accelerator::new(CMD_OR_CTRL | Modifiers::ALT, Code::KeyH))
}
#[cfg(target_os = "macos")]
PredefinedMenuItemType::CloseWindow => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyW)),
#[cfg(not(target_os = "macos"))]
PredefinedMenuItemType::CloseWindow => {
Some(Accelerator::new(Some(Modifiers::ALT), Code::F4))
}
PredefinedMenuItemType::CloseWindow => Some(Accelerator::new(Modifiers::ALT, Code::F4)),
#[cfg(target_os = "macos")]
PredefinedMenuItemType::Quit => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyQ)),
PredefinedMenuItemType::Quit => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyQ)),
_ => None,
}
}
Expand Down
7 changes: 1 addition & 6 deletions src/items/submenu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,19 +236,14 @@ impl ContextMenu for Submenu {
#[cfg(target_os = "linux")]
fn show_context_menu_for_gtk_window(
&self,
w: &gtk::Window,
w: &gtk4::Window,
position: Option<Position>,
) -> bool {
self.inner
.borrow_mut()
.show_context_menu_for_gtk_window(w, position)
}

#[cfg(target_os = "linux")]
fn gtk_context_menu(&self) -> gtk::Menu {
self.inner.borrow_mut().gtk_context_menu()
}

#[cfg(target_os = "macos")]
unsafe fn show_context_menu_for_nsview(
&self,
Expand Down
8 changes: 1 addition & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,16 +378,10 @@ pub trait ContextMenu {
#[cfg(target_os = "linux")]
fn show_context_menu_for_gtk_window(
&self,
w: &gtk::Window,
w: &gtk4::Window,
position: Option<dpi::Position>,
) -> bool;

/// Get the underlying gtk menu reserved for context menus.
///
/// The returned [`gtk::Menu`] is valid as long as the `ContextMenu` is.
#[cfg(target_os = "linux")]
fn gtk_context_menu(&self) -> gtk::Menu;

/// Shows this menu as a context menu for the specified `NSView`.
///
/// - `position` is relative to the window top-left corner, if `None`, the cursor position is used.
Expand Down
Loading

0 comments on commit 3b5bbfe

Please sign in to comment.