linebender / linebender/druid

Mac: Context menu on non-focused window disappears without action on click

Open
#2,417 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
9.7k
Forks
565
PR merge metrics
No merged PRs in 30d

Description

When the application window does not have focus, and I open a context menu by right-clicking the window, the window stays non-focused on Mac.

The context menu still appears, still highlights the options under my mouse, etc. But when I click on an option from the context menu in this state, the context menu disappears without performing the action that it would normally perform. For an action that doesn't display any immediate sign of it working other than the context menu closing, this can fool users into thinking they used the context menu to perform the action, when they didn't.

I can think of 2 reasonable sets of behaviors:

  1. Perform the action and close the context menu.
  2. Give focus without performing the action or closing, allowing the user to click again now that it has focus.

With the current confusing behavior being:

  • "Silently" fail with no action and close the context menu.

On Windows, right-clicking the window gives focus immediately. To someone not paying attention to which window has focus, this looks most like (1).

Below is a small Context Menu Example showing this behavior. On control-clicking, it gets focus and behaves as expected. But on right-clicking with a mouse, it does not get focus on Mac, and this confusing behavior appears:

use druid::widget::prelude::*;
use druid::widget::{Align, Controller, Label};
use druid::{
    AppLauncher, Data, Env, Lens, LocalizedString, Menu, MenuItem, Selector, Widget, WidgetExt,
    WindowDesc,
};

const WINDOW_TITLE: LocalizedString<CounterState> = LocalizedString::new("Context Menu Example");

#[derive(Clone, Data, Lens)]
struct CounterState {
    i: i64,
}

struct CounterController;

const CONTEXT_MENU_COUNTER_INCREMENT: Selector = Selector::new("context-menu-counter-increment");
const CONTEXT_MENU_COUNTER_DECREMENT: Selector = Selector::new("context-menu-counter-decrement");

impl<W: Widget<CounterState>> Controller<CounterState, W> for CounterController {
    fn event(
        &mut self,
        child: &mut W,
        ctx: &mut EventCtx,
        event: &Event,
        data: &mut CounterState,
        env: &Env,
    ) {
        match event {
            Event::MouseUp(event) => {
                if event.button.is_right() || (event.button.is_left() && event.mods.ctrl()) {
                    ctx.show_context_menu::<CounterState>(
                        Menu::new("Counter")
                            .entry(
                                MenuItem::new("Increment").command(CONTEXT_MENU_COUNTER_INCREMENT),
                            )
                            .entry(
                                MenuItem::new("Decrement").command(CONTEXT_MENU_COUNTER_DECREMENT),
                            ),
                        event.pos,
                    );
                }
            }
            Event::Command(command) => {
                if command.is(CONTEXT_MENU_COUNTER_INCREMENT) {
                    data.i += 1;
                } else if command.is(CONTEXT_MENU_COUNTER_DECREMENT) {
                    data.i -= 1;
                }
            }
            _ => {}
        }
        // Always pass on the event!
        child.event(ctx, event, data, env)
    }
}

fn main() {
    // describe the main window
    let main_window = WindowDesc::new(build_root_widget())
        .title(WINDOW_TITLE)
        .window_size((400.0, 400.0));

    // create the initial app state
    let initial_state = CounterState { i: 0 };

    // start the application
    AppLauncher::with_window(main_window)
        .launch(initial_state)
        .expect("Failed to launch application");
}

fn build_root_widget() -> impl Widget<CounterState> {
    // a label that will determine its text based on the current app data.
    let label = Label::new(|data: &CounterState, _env: &Env| format!("Counter: {}", data.i));

    // center the a widget in the available space
    Align::centered(label).controller(CounterController)
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the Context Menu Example and trace Event::MouseUp through ctx.show_context_menu on macOS, comparing right-click with control-click behavior. Identify the platform handling responsible for focus and menu activation, then verify that selecting an option from an unfocused window performs the command and closes the menu as expected.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.