Skip to content

Commit

Permalink
Linux context menu (lapce#101)
Browse files Browse the repository at this point in the history
* in progress

* style change

* finish menu action

* make context menu only for Linux

* put context menu under linux only

* tiny change
  • Loading branch information
dzhou121 authored and jrmoulton committed Sep 16, 2023
1 parent 7fd314d commit b5b4c8d
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 16 deletions.
5 changes: 5 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ pub(crate) enum AppUpdateEvent {
RequestTimer {
timer: Timer,
},
#[cfg(target_os = "linux")]
MenuAction {
window_id: WindowId,
action_id: usize,
},
}

pub(crate) fn add_app_update_event(event: AppUpdateEvent) {
Expand Down
11 changes: 11 additions & 0 deletions src/app_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@ impl ApplicationHandle {
AppUpdateEvent::RequestTimer { timer } => {
self.request_timer(timer, control_flow);
}
#[cfg(target_os = "linux")]
AppUpdateEvent::MenuAction {
window_id,
action_id,
} => {
let window_handle = match self.window_handles.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
window_handle.menu_action(action_id);
}
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,14 +586,14 @@ impl AppState {
.0
}

pub(crate) fn update_context_menu(&mut self, mut menu: Menu) {
pub(crate) fn update_context_menu(&mut self, menu: &mut Menu) {
if let Some(action) = menu.item.action.take() {
self.context_menu.insert(menu.item.id as usize, action);
}
for child in menu.children {
for child in menu.children.iter_mut() {
match child {
crate::menu::MenuEntry::Separator => {}
crate::menu::MenuEntry::Item(mut item) => {
crate::menu::MenuEntry::Item(item) => {
if let Some(action) = item.action.take() {
self.context_menu.insert(item.id as usize, action);
}
Expand Down
4 changes: 2 additions & 2 deletions src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ impl Menu {

pub struct MenuItem {
pub(crate) id: u64,
title: String,
pub(crate) title: String,
// key: Option<HotKey>,
selected: Option<bool>,
enabled: bool,
pub(crate) enabled: bool,
pub(crate) action: Option<Box<dyn Fn()>>,
}

Expand Down
199 changes: 188 additions & 11 deletions src/window_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ use std::{
time::{Duration, Instant},
};

use floem_reactive::{with_scope, Scope};
use floem_reactive::{with_scope, RwSignal, Scope};
use floem_renderer::Renderer;
use kurbo::{Affine, Point, Rect, Size, Vec2};

#[cfg(target_os = "linux")]
use winit::window::WindowId;
use winit::{
dpi::{LogicalPosition, LogicalSize},
event::{ElementState, Ime, MouseButton, MouseScrollDelta},
keyboard::{Key, ModifiersState},
window::CursorIcon,
};

#[cfg(target_os = "linux")]
use crate::views::{container_box, stack, Decorators};
use crate::{
action::exec_after,
animate::{AnimPropKind, AnimUpdateMsg, AnimValue, AnimatedProp, SizeUnit},
Expand Down Expand Up @@ -46,11 +51,14 @@ pub(crate) struct WindowHandle {
pub(crate) view: Box<dyn View>,
app_state: AppState,
paint_state: PaintState,
size: Size,
size: RwSignal<Size>,
pub(crate) scale: f64,
pub(crate) modifiers: ModifiersState,
pub(crate) cursor_position: Point,
pub(crate) window_position: Point,
pub(crate) last_pointer_down: Option<(u8, Instant)>,
#[cfg(target_os = "linux")]
pub(crate) context_menu: RwSignal<Option<(Menu, Point)>>,
}

impl WindowHandle {
Expand All @@ -64,26 +72,52 @@ impl WindowHandle {
set_current_view(id);

let scope = Scope::new();
let cx = ViewContext { id };
let view = ViewContext::with_context(cx, || with_scope(scope, move || view_fn(window_id)));

let scale = window.scale_factor();
let size: LogicalSize<f64> = window.inner_size().to_logical(scale);
let size = Size::new(size.width, size.height);
let paint_state = PaintState::new(&window, scale, size * scale);
let size = scope.create_rw_signal(Size::new(size.width, size.height));

#[cfg(target_os = "linux")]
let context_menu = scope.create_rw_signal(None);
let cx = ViewContext { id };

#[cfg(not(target_os = "linux"))]
let view = ViewContext::with_context(cx, || with_scope(scope, move || view_fn(window_id)));

#[cfg(target_os = "linux")]
let view = ViewContext::with_context(cx, || {
with_scope(scope, move || {
Box::new(
stack(|| {
(
container_box(move || view_fn(window_id))
.style(|s| s.size_pct(100.0, 100.0)),
context_menu_view(scope, window_id, context_menu, size),
)
})
.style(|s| s.size_pct(100.0, 100.0)),
)
})
});

let paint_state = PaintState::new(&window, scale, size.get_untracked() * scale);
let mut window_handle = Self {
window: Some(window),
scope: Scope::new(),
scope,
view,
app_state: AppState::new(),
paint_state,
size,
scale,
modifiers: ModifiersState::default(),
cursor_position: Point::ZERO,
window_position: Point::ZERO,
#[cfg(target_os = "linux")]
context_menu,
last_pointer_down: None,
};
window_handle.app_state.set_root_size(size);
window_handle.app_state.set_root_size(size.get_untracked());
window_handle
}

Expand Down Expand Up @@ -240,7 +274,7 @@ impl WindowHandle {
}

pub(crate) fn size(&mut self, size: Size) {
self.size = size;
self.size.set(size);
self.app_state.update_screen_size_bp(size);
self.event(Event::WindowResized(size));
let scale = self.scale * self.app_state.scale;
Expand All @@ -252,6 +286,7 @@ impl WindowHandle {
}

pub(crate) fn position(&mut self, point: Point) {
self.window_position = point;
self.event(Event::WindowMoved(point));
}

Expand Down Expand Up @@ -587,11 +622,16 @@ impl WindowHandle {
state.popout_menu = Some(menu);
}
UpdateMessage::ShowContextMenu { menu, pos } => {
let menu = menu.popup();
let mut menu = menu.popup();
let platform_menu = menu.platform_menu();
cx.app_state.context_menu.clear();
cx.app_state.update_context_menu(menu);
cx.app_state.update_context_menu(&mut menu);
#[cfg(target_os = "macos")]
self.show_context_menu(platform_menu, pos);
#[cfg(target_os = "windows")]
self.show_context_menu(platform_menu, pos);
#[cfg(target_os = "linux")]
self.show_context_menu(menu, platform_menu, pos);
}
UpdateMessage::WindowMenu { menu } => {
// let platform_menu = menu.platform_menu();
Expand Down Expand Up @@ -851,7 +891,10 @@ impl WindowHandle {
}

#[cfg(target_os = "linux")]
fn show_context_menu(&self, _menu: winit::menu::Menu, _pos: Option<Point>) {}
fn show_context_menu(&self, menu: Menu, _platform_menu: winit::menu::Menu, pos: Option<Point>) {
self.context_menu
.set(Some((menu, pos.unwrap_or(self.cursor_position))));
}

pub(crate) fn menu_action(&mut self, id: usize) {
set_current_view(self.view.id());
Expand Down Expand Up @@ -891,3 +934,137 @@ pub(crate) fn set_current_view(id: Id) {
*running.borrow_mut() = id;
});
}

#[cfg(target_os = "linux")]
fn context_menu_view(
cx: Scope,
window_id: WindowId,
context_menu: RwSignal<Option<(Menu, Point)>>,
window_size: RwSignal<Size>,
) -> impl View {
use floem_reactive::create_effect;
use peniko::Color;

use crate::{
app::{add_app_update_event, AppUpdateEvent},
views::{empty, label, list},
};

let context_menu_items = cx.create_memo(move |_| {
context_menu.with(|menu| {
menu.as_ref().map(|menu: &(Menu, Point)| {
menu.0
.children
.iter()
.map(|e| match e {
crate::menu::MenuEntry::Separator => None,
crate::menu::MenuEntry::Item(i) => {
Some((Some(i.id), i.enabled, i.title.clone()))
}
crate::menu::MenuEntry::SubMenu(m) => {
Some((None, m.item.enabled, m.item.title.clone()))
}
})
.collect::<Vec<Option<(Option<u64>, bool, String)>>>()
})
})
});
let context_menu_size = cx.create_rw_signal(Size::ZERO);
let view = list(
move || context_menu_items.get().unwrap_or_default(),
move |s| s.clone(),
move |s| {
if let Some((id, enabled, s)) = s {
container_box(|| Box::new(label(move || s.clone())))
.on_click(move |_| {
context_menu.set(None);
if let Some(id) = id {
add_app_update_event(AppUpdateEvent::MenuAction {
window_id,
action_id: id as usize,
});
}
true
})
.on_secondary_click(move |_| {
context_menu.set(None);
if let Some(id) = id {
add_app_update_event(AppUpdateEvent::MenuAction {
window_id,
action_id: id as usize,
});
}
true
})
.disabled(move || !enabled)
.style(|s| s.min_width_pct(100.0).padding_horiz_px(20.0))
.hover_style(|s| s.border_radius(10.0).background(Color::rgb8(65, 65, 65)))
.active_style(|s| s.border_radius(10.0).background(Color::rgb8(92, 92, 92)))
.disabled_style(|s| s.color(Color::rgb8(92, 92, 92)))
} else {
container_box(|| {
Box::new(empty().style(|s| {
s.width_pct(100.0)
.height_px(1.0)
.margin_vert_px(5.0)
.background(Color::rgb8(92, 92, 92))
}))
})
.style(|s| s.min_width_pct(100.0).padding_horiz_px(20.0))
}
},
)
.on_resize(move |rect| {
context_menu_size.set(rect.size());
})
.on_event(EventListener::PointerDown, move |_| true)
.keyboard_navigatable()
.on_event(EventListener::KeyDown, move |event| {
if let Event::KeyDown(event) = event {
if event.key.logical_key == Key::Escape {
context_menu.set(None);
}
}
true
})
.on_event(EventListener::FocusLost, move |_| {
context_menu.set(None);
true
})
.style(move |s| {
let window_size = window_size.get();
let menu_size = context_menu_size.get();
let is_acitve = context_menu.with(|m| m.is_some());
let mut pos = context_menu.with(|m| m.as_ref().map(|(_, pos)| *pos).unwrap_or_default());
if pos.x + menu_size.width > window_size.width {
pos.x = window_size.width - menu_size.width;
}
if pos.y + menu_size.height > window_size.height {
pos.y = window_size.height - menu_size.height;
}
s.absolute()
.flex_col()
.border_radius(10.0)
.background(Color::rgb8(44, 44, 44))
.color(Color::rgb8(201, 201, 201))
.z_index(999)
.line_height(2.0)
.padding_px(5.0)
.margin_left_px(pos.x as f32)
.margin_top_px(pos.y as f32)
.cursor(CursorStyle::Default)
.apply_if(!is_acitve, |s| s.hide())
.box_shadow_blur(5.0)
.box_shadow_color(Color::BLACK)
});

let id = view.id();

create_effect(move |_| {
if context_menu.with(|m| m.is_some()) {
id.request_focus();
}
});

view
}

0 comments on commit b5b4c8d

Please sign in to comment.