Skip to content

Commit

Permalink
Enables/disables window menu items according to window's state. (#171)
Browse files Browse the repository at this point in the history
* parent 856ba4a
author Philipp Kaeser <[email protected]> 1736885410 +0000
committer Philipp Kaeser <[email protected]> 1739545497 +0100

Enables/disables window menu items according to window's state.

* Updates tests to reflect workspace-changed signal.
  • Loading branch information
phkaeser authored Feb 14, 2025
1 parent 8724b26 commit 9c45cc8
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 27 deletions.
2 changes: 1 addition & 1 deletion doc/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Support for visual effects to improve usability, but not for pure show.
* [done] Available also for X11 windows.
* Available as (hardcoded) application menu.
* Menu with submenus.
* Window menu adapting to window state.
* [done] Window menu adapting to window state.
(Eg. "Maximize" shown when not maximized, otherwise: "restore".)
* When positioning the root menu, keep it entirely within the desktop area.

Expand Down
51 changes: 50 additions & 1 deletion src/action_item.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,27 @@ wlmaker_action_item_t *wlmaker_action_item_create(
return action_item_ptr;
}

/* ------------------------------------------------------------------------- */
wlmaker_action_item_t *wlmaker_action_item_create_from_desc(
const wlmaker_action_item_desc_t *desc_ptr,
void *dest_ptr,
const wlmtk_menu_item_style_t *style_ptr,
wlmaker_server_t *server_ptr,
wlmtk_env_t *env_ptr)
{
wlmaker_action_item_t *action_item_ptr = wlmaker_action_item_create(
desc_ptr->text_ptr,
style_ptr,
desc_ptr->action,
server_ptr,
env_ptr);
if (NULL == action_item_ptr) return NULL;

*(wlmaker_action_item_t**)(
(uint8_t*)dest_ptr + desc_ptr->destination_ofs) = action_item_ptr;
return action_item_ptr;
}

/* ------------------------------------------------------------------------- */
void wlmaker_action_item_destroy(wlmaker_action_item_t *action_item_ptr)
{
Expand Down Expand Up @@ -134,4 +155,32 @@ void _wlmaker_action_item_clicked(wlmtk_menu_item_t *menu_item_ptr)
}
}

/* == End of action_item.c ================================================== */
/* == Unit tests =========================================================== */

static void _wlmaker_action_item_test_create(bs_test_t *test_ptr);

/** Test cases for action items. */
const bs_test_case_t wlmaker_action_item_test_cases[] = {
{ 1, "create", _wlmaker_action_item_test_create },
{ 0, NULL, NULL },
};

/* ------------------------------------------------------------------------- */
/** Tests creation the menu item. */
void _wlmaker_action_item_test_create(bs_test_t *test_ptr)
{
wlmaker_action_item_t *ai_ptr = NULL;
wlmaker_action_item_desc_t desc = { "text", 42, 0 };
wlmtk_menu_item_style_t style = {};
wlmaker_server_t server = {};

BS_TEST_VERIFY_TRUE(
test_ptr,
wlmaker_action_item_create_from_desc(
&desc, &ai_ptr, &style, &server, NULL));

BS_TEST_VERIFY_NEQ(test_ptr, NULL, ai_ptr);
wlmaker_action_item_destroy(ai_ptr);
}

/* == End of action_item.c ================================================= */
34 changes: 34 additions & 0 deletions src/action_item.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ typedef struct _wlmaker_action_item_t wlmaker_action_item_t;
extern "C" {
#endif // __cplusplus

/** Descriptor for creating a menu item triggering an action. */
typedef struct {
/** Text for the menu item. */
const char *text_ptr;
/** The action to trigger. */
wlmaker_action_t action;
/**
* Where to store the @ref wlmaker_action_item_t, relative to the
* `dest_ptr` argument of @ref wlmaker_action_item_create_from_desc.
*/
size_t destination_ofs;
} wlmaker_action_item_desc_t;

/**
* Creates a menu item that triggers a @ref wlmaker_action_t.
*
Expand All @@ -49,6 +62,24 @@ wlmaker_action_item_t *wlmaker_action_item_create(
wlmaker_server_t *server_ptr,
wlmtk_env_t *env_ptr);

/**
* Creates a menu item triggering an action item from a descriptor.
*
* @param desc_ptr
* @param dest_ptr
* @param style_ptr
* @param server_ptr
* @param env_ptr
*
* @return Pointer to the item's handle or NULL on error.
*/
wlmaker_action_item_t *wlmaker_action_item_create_from_desc(
const wlmaker_action_item_desc_t *desc_ptr,
void *dest_ptr,
const wlmtk_menu_item_style_t *style_ptr,
wlmaker_server_t *server_ptr,
wlmtk_env_t *env_ptr);

/**
* Destroys the action-triggering menu item.
*
Expand All @@ -60,6 +91,9 @@ void wlmaker_action_item_destroy(wlmaker_action_item_t *action_item_ptr);
wlmtk_menu_item_t *wlmaker_action_item_menu_item(
wlmaker_action_item_t *action_item_ptr);

/** Unit test cases. */
extern const bs_test_case_t wlmaker_action_item_test_cases[];

#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
Expand Down
155 changes: 130 additions & 25 deletions src/tl_menu.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,82 @@
struct _wlmaker_tl_menu_t {
/** Pointer to the window's @ref wlmtk_menu_t. */
wlmtk_menu_t *menu_ptr;

/** Listener for @ref wlmtk_window_events_t::state_changed. */
struct wl_listener window_state_changed_listener;

/** Action item for 'Maximize'. */
wlmaker_action_item_t *maximize_ai_ptr;
/** Action item for 'Unmaximize'. */
wlmaker_action_item_t *unmaximize_ai_ptr;
/** Action item for 'Fullscreen'. */
wlmaker_action_item_t *fullscreen_ai_ptr;
/** Action item for 'Shade'. */
wlmaker_action_item_t *shade_ai_ptr;
/** Action item for 'Unshade'. */
wlmaker_action_item_t *unshade_ai_ptr;
/** Action item for 'to previous workspace'. */
wlmaker_action_item_t *prev_ws_ai_ptr;
/** Action item for 'to next workspace'. */
wlmaker_action_item_t *next_ws_ai_ptr;
/** Action item for 'close'. */
wlmaker_action_item_t *close_ai_ptr;
};

/** Temporary: Struct for defining an item for the window menu. */
typedef struct {
/** Text to use for the menu item. */
const char *text_ptr;
/** Action to be executed for that menu item. */
wlmaker_action_t action;
} wlmaker_window_menu_item_t;
static void _wlmaker_tl_menu_handle_window_state_changed(
struct wl_listener *listener_ptr,
void *data_ptr);

/* == Data ================================================================= */

/** Menu items for the XDG toplevel's window menu. */
static const wlmaker_window_menu_item_t _xdg_toplevel_menu_items[] = {
{ "Maximize", WLMAKER_ACTION_WINDOW_MAXIMIZE },
{ "Unmaximize", WLMAKER_ACTION_WINDOW_UNMAXIMIZE },
{ "Fullscreen", WLMAKER_ACTION_WINDOW_TOGGLE_FULLSCREEN },
{ "Shade", WLMAKER_ACTION_WINDOW_SHADE },
{ "Unshade", WLMAKER_ACTION_WINDOW_UNSHADE },
{ "To prev. workspace", WLMAKER_ACTION_WINDOW_TO_PREVIOUS_WORKSPACE },
{ "To next workspace", WLMAKER_ACTION_WINDOW_TO_NEXT_WORKSPACE },
{ "Close", WLMAKER_ACTION_WINDOW_CLOSE },
{ NULL, 0 } // Sentinel.
static const wlmaker_action_item_desc_t _tl_menu_items[] = {
{
"Maximize",
WLMAKER_ACTION_WINDOW_MAXIMIZE,
offsetof(wlmaker_tl_menu_t, maximize_ai_ptr)
},
{
"Unmaximize",
WLMAKER_ACTION_WINDOW_UNMAXIMIZE,
offsetof(wlmaker_tl_menu_t, unmaximize_ai_ptr)
},
{
"Fullscreen",
WLMAKER_ACTION_WINDOW_TOGGLE_FULLSCREEN,
offsetof(wlmaker_tl_menu_t, fullscreen_ai_ptr)
},
{
"Shade",
WLMAKER_ACTION_WINDOW_SHADE,
offsetof(wlmaker_tl_menu_t, shade_ai_ptr)

},
{
"Unshade",
WLMAKER_ACTION_WINDOW_UNSHADE,
offsetof(wlmaker_tl_menu_t, unshade_ai_ptr)

},
{
"To prev. workspace",
WLMAKER_ACTION_WINDOW_TO_PREVIOUS_WORKSPACE,
offsetof(wlmaker_tl_menu_t, prev_ws_ai_ptr)

},
{
"To next workspace",
WLMAKER_ACTION_WINDOW_TO_NEXT_WORKSPACE,
offsetof(wlmaker_tl_menu_t, next_ws_ai_ptr)

},
{
"Close",
WLMAKER_ACTION_WINDOW_CLOSE,
offsetof(wlmaker_tl_menu_t, close_ai_ptr)

},
{ NULL, 0, 0 } // Sentinel.
};

/* == Exported methods ===================================================== */
Expand All @@ -64,25 +118,36 @@ wlmaker_tl_menu_t *wlmaker_tl_menu_create(
if (NULL == tl_menu_ptr) return NULL;
tl_menu_ptr->menu_ptr = wlmtk_window_menu(window_ptr);

for (const wlmaker_window_menu_item_t *i_ptr = &_xdg_toplevel_menu_items[0];
i_ptr->text_ptr != NULL;
++i_ptr) {
for (const wlmaker_action_item_desc_t *desc_ptr = &_tl_menu_items[0];
NULL != desc_ptr->text_ptr;
++desc_ptr) {

wlmaker_action_item_t *action_item_ptr = wlmaker_action_item_create(
i_ptr->text_ptr,
wlmaker_action_item_t *ai_ptr = wlmaker_action_item_create_from_desc(
desc_ptr,
tl_menu_ptr,
&server_ptr->style.menu.item,
i_ptr->action,
server_ptr,
server_ptr->env_ptr);
if (NULL == action_item_ptr) {
if (NULL == ai_ptr) {
bs_log(BS_ERROR, "Failed wlmaker_action_item_create_from_desc()");
wlmaker_tl_menu_destroy(tl_menu_ptr);
return NULL;
}

wlmtk_menu_add_item(
tl_menu_ptr->menu_ptr,
wlmaker_action_item_menu_item(action_item_ptr));
wlmaker_action_item_menu_item(ai_ptr));
}

// Connect state listener and initialize state.
wlmtk_util_connect_listener_signal(
&wlmtk_window_events(window_ptr)->state_changed,
&tl_menu_ptr->window_state_changed_listener,
_wlmaker_tl_menu_handle_window_state_changed);
_wlmaker_tl_menu_handle_window_state_changed(
&tl_menu_ptr->window_state_changed_listener,
window_ptr);

return tl_menu_ptr;
}

Expand All @@ -94,4 +159,44 @@ void wlmaker_tl_menu_destroy(wlmaker_tl_menu_t *tl_menu_ptr)

/* == Local (static) methods =============================================== */

/* ------------------------------------------------------------------------- */
/** Handles state changes: Updates the menu items accordingly. */
void _wlmaker_tl_menu_handle_window_state_changed(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmaker_tl_menu_t *tl_menu_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_tl_menu_t, window_state_changed_listener);
wlmtk_window_t *window_ptr = data_ptr;

wlmtk_menu_item_set_enabled(
wlmaker_action_item_menu_item(tl_menu_ptr->shade_ai_ptr),
!wlmtk_window_is_shaded(window_ptr));
wlmtk_menu_item_set_enabled(
wlmaker_action_item_menu_item(tl_menu_ptr->unshade_ai_ptr),
wlmtk_window_is_shaded(window_ptr));

wlmtk_menu_item_set_enabled(
wlmaker_action_item_menu_item(tl_menu_ptr->fullscreen_ai_ptr),
!wlmtk_window_is_fullscreen(window_ptr));

wlmtk_menu_item_set_enabled(
wlmaker_action_item_menu_item(tl_menu_ptr->maximize_ai_ptr),
!wlmtk_window_is_maximized(window_ptr));
wlmtk_menu_item_set_enabled(
wlmaker_action_item_menu_item(tl_menu_ptr->unmaximize_ai_ptr),
wlmtk_window_is_maximized(window_ptr));

if (NULL != wlmtk_window_get_workspace(window_ptr)) {
bs_dllist_node_t *ws_dlnode_ptr = wlmtk_dlnode_from_workspace(
wlmtk_window_get_workspace(window_ptr));
wlmtk_menu_item_set_enabled(
wlmaker_action_item_menu_item(tl_menu_ptr->prev_ws_ai_ptr),
NULL != ws_dlnode_ptr->prev_ptr);
wlmtk_menu_item_set_enabled(
wlmaker_action_item_menu_item(tl_menu_ptr->next_ws_ai_ptr),
NULL != ws_dlnode_ptr->next_ptr);
}
}

/* == End of tl_menu.c ===================================================== */
6 changes: 6 additions & 0 deletions src/toolkit/window.c
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,8 @@ void wlmtk_window_set_workspace(
wlmtk_workspace_t *workspace_ptr)
{
window_ptr->workspace_ptr = workspace_ptr;

wl_signal_emit(&window_ptr->events.state_changed, window_ptr);
}

/* ------------------------------------------------------------------------- */
Expand Down Expand Up @@ -1544,6 +1546,8 @@ void test_maximize(bs_test_t *test_ptr)

// Window must be mapped to get maximized: Need workspace dimensions.
wlmtk_workspace_map_window(ws_ptr, fw_ptr->window_ptr);
BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls);
wlmtk_util_clear_test_listener(&l);

// Set up initial organic size, and verify.
wlmtk_window_request_position_and_size(fw_ptr->window_ptr, 20, 10, 200, 100);
Expand Down Expand Up @@ -1631,6 +1635,8 @@ void test_fullscreen(bs_test_t *test_ptr)

wlmtk_window_set_server_side_decorated(fw_ptr->window_ptr, true);
wlmtk_workspace_map_window(ws_ptr, fw_ptr->window_ptr);
BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls);
wlmtk_util_clear_test_listener(&l);

BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->fake_content_ptr->activated);
BS_TEST_VERIFY_EQ(
Expand Down
5 changes: 5 additions & 0 deletions src/toolkit/window.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ typedef struct {
* - @ref wlmtk_window_is_maximized
* - @ref wlmtk_window_is_fullscreen
* - @ref wlmtk_window_is_shaded
*
* The signal is also raised when the window's workspace is changed.
* Retrieve through @ref wlmtk_window_get_workspace.
*
* data_ptr points to the window state (@ref wlmtk_window_t).
*/
struct wl_signal state_changed;
} wlmtk_window_events_t;
Expand Down
2 changes: 2 additions & 0 deletions src/wlmaker_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/

#include "action.h"
#include "action_item.h"
#include "clip.h"
#include "config.h"
#include "corner.h"
Expand All @@ -31,6 +32,7 @@
/** WLMaker unit tests. */
const bs_test_set_t wlmaker_tests[] = {
{ 1, "action", wlmaker_action_test_cases },
{ 1, "action_item", wlmaker_action_item_test_cases },
{ 1, "clip", wlmaker_clip_test_cases },
{ 1, "config", wlmaker_config_test_cases },
{ 1, "corner", wlmaker_corner_test_cases },
Expand Down

0 comments on commit 9c45cc8

Please sign in to comment.