diff --git a/CMakeLists.txt b/CMakeLists.txt index ec20896e..651e5bc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ PKG_CHECK_MODULES(XKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon>=1.0.3) # 1.4.1) # Configuration. Remove CMakeCache.txt to rerun... OPTION(config_DEBUG "Include debugging information" ON) OPTION(config_OPTIM "Optimizations" OFF) +OPTION(config_DOXYGEN_CRITICAL "Whether to fail on doxygen warnings" OFF) # Toplevel compile options, for GCC. IF(CMAKE_C_COMPILER_ID STREQUAL "GNU") @@ -79,7 +80,6 @@ ADD_SUBDIRECTORY(icons) ADD_SUBDIRECTORY(protocols) ADD_SUBDIRECTORY(third_party/protocols) ADD_SUBDIRECTORY(src) -ADD_SUBDIRECTORY(src/decorations) ADD_SUBDIRECTORY(src/toolkit) # Adds submodules last, to permit checking on already-existing targets. diff --git a/dependencies/drm b/dependencies/drm index 98e1db50..a0b01143 160000 --- a/dependencies/drm +++ b/dependencies/drm @@ -1 +1 @@ -Subproject commit 98e1db501173303e58ef6a1def94ab7a2d84afc1 +Subproject commit a0b011439d44e7d79ffed6dd2d372e60dc7f3b1b diff --git a/dependencies/hwdata b/dependencies/hwdata index 2726a2e3..e27f08bd 160000 --- a/dependencies/hwdata +++ b/dependencies/hwdata @@ -1 +1 @@ -Subproject commit 2726a2e35ebe11681da6091a99ed304c9f707c90 +Subproject commit e27f08bda517100746000dacdd882b6a7e7ce19a diff --git a/dependencies/libdisplay-info b/dependencies/libdisplay-info index 49af17a3..ae6cb524 160000 --- a/dependencies/libdisplay-info +++ b/dependencies/libdisplay-info @@ -1 +1 @@ -Subproject commit 49af17a3e653b6e07a0f508da0c83c4ee3cd9077 +Subproject commit ae6cb5242e40563bbea0048690e432a223f6f452 diff --git a/dependencies/pixman b/dependencies/pixman index e4c878d1..b4b789df 160000 --- a/dependencies/pixman +++ b/dependencies/pixman @@ -1 +1 @@ -Subproject commit e4c878d17942346dce5f54b25d7624440ef47de6 +Subproject commit b4b789df5b39cecdb598b35a3aa2ca9637029564 diff --git a/dependencies/seatd b/dependencies/seatd index 1bd042e5..0746edbe 160000 --- a/dependencies/seatd +++ b/dependencies/seatd @@ -1 +1 @@ -Subproject commit 1bd042e5b0a524fb7d30953179474e60974aa6ee +Subproject commit 0746edbeaeb1c94a54bf833f6167b4a6b8237cbf diff --git a/dependencies/wayland b/dependencies/wayland index 4a7348e4..50ea9c5b 160000 --- a/dependencies/wayland +++ b/dependencies/wayland @@ -1 +1 @@ -Subproject commit 4a7348e48c05962c7ca5a92f055622263a40242c +Subproject commit 50ea9c5b1c08bac30be365dca05716a97ea65a92 diff --git a/dependencies/wayland-protocols b/dependencies/wayland-protocols index bbe9298e..87e0ce44 160000 --- a/dependencies/wayland-protocols +++ b/dependencies/wayland-protocols @@ -1 +1 @@ -Subproject commit bbe9298e85220d8cd40ef802671ec575ba81367f +Subproject commit 87e0ce44f32e7bd00c9c609010204d794945b663 diff --git a/dependencies/wlroots b/dependencies/wlroots index fffa1908..5de9e1a9 160000 --- a/dependencies/wlroots +++ b/dependencies/wlroots @@ -1 +1 @@ -Subproject commit fffa1908af47d7cb1691425a0b6d9dccb01f1365 +Subproject commit 5de9e1a99d6642c2d09d589aa37ff0a8945dcee1 diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index f7e0d7e3..ae1e6579 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -17,6 +17,30 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.13) FIND_PACKAGE(Doxygen) IF(DOXYGEN_FOUND) + FIND_FILE(PLANTUML_JAR + NAMES plantuml.jar + HINTS ENV{PLANTUML_JAR_PATH} + PATHS + /usr/global/share/java/plantuml/ + /usr/local/share/java/plantuml/ + /usr/share/java/ + /usr/local/share/java/ + /usr/share/plantuml/) + IF(PLANTUML_JAR) + SET(DOXYGEN_PLANTUML_JAR_FILE ${PLANTUML_JAR}) + ELSE() + SET(DOXYGEN_PLANTUML_JAR_FILE "") + MESSAGE( + NOTICE + "Did not find plantuml.jar -- Will not generate class diagrams.") + ENDIF(PLANTUML_JAR_FOUND) + + IF(config_DOXYGEN_CRITICAL) + SET(DOXYGEN_WARN_AS_ERROR "YES") + ELSE(config_DOXYGEN_CRITICAL) + SET(DOXYGEN_WARN_AS_ERROR "NO") + ENDIF(config_DOXYGEN_CRITICAL) + # set input and output files SET(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) SET(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index e6c20976..f41d70f5 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -828,7 +828,7 @@ WARN_NO_PARAMDOC = NO # Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. -WARN_AS_ERROR = YES +WARN_AS_ERROR = @DOXYGEN_WARN_AS_ERROR@ # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which @@ -857,7 +857,6 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = @PROJECT_SOURCE_DIR@/src \ - @PROJECT_SOURCE_DIR@/src/decorations \ @PROJECT_SOURCE_DIR@/src/toolkit \ @PROJECT_SOURCE_DIR@/apps/ \ @PROJECT_SOURCE_DIR@/apps/libwlclient @@ -2542,7 +2541,7 @@ DIAFILE_DIRS = # generate a warning when it encounters a \startuml command in this case and # will not generate output for the diagram. -PLANTUML_JAR_PATH = +PLANTUML_JAR_PATH = @DOXYGEN_PLANTUML_JAR_FILE@ # When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a # configuration file for plantuml. diff --git a/doc/ROADMAP.md b/doc/ROADMAP.md index a9b71844..413e35de 100644 --- a/doc/ROADMAP.md +++ b/doc/ROADMAP.md @@ -65,6 +65,14 @@ Support for visual effects to improve usability, but not for pure show. ## Plan for 0.2 +* Issues to fix: + * [done] Fix out-of-sync display of server-side decoration and window content when resizing. + * Fix assertion crash when mouse is pressed, then moved to another toplevel, then released. + * Hide window border when not having server-side decoration. + * Fix issue with Chrome: Enabling "Use system title and boders" will pick a slightly small decoration. + * Fix issue on resizing: When moving the mouse too quickly, focus is lost and the resizing stops. + * Fix issue on fullscreen: The window border is kept, having the window off by 1 pixel. + * Experimental support for Dock Apps * [done] Experimental wayland protocol for Apps to declare icon surfaces. * Surfaces will be shown in either tile container, clip or dock area, @@ -76,10 +84,66 @@ Support for visual effects to improve usability, but not for pure show. * Configurable keyboard map (in code or commandline arg) +* Support `xdg_shell`, based on toolkit. + * [done] XDG Popups. + * [done] Move and Resize, compliant with asynchronous ops. + * [done] Maximize. + * [done] Set title. + * [done] fullscreen. + * minimize. + * show window menu. + * set_parent. + * set app ID. + +* Support `layer_shell`, based on toolkit. + +* Support window decoration protocol, based on toolkit. + * [done] Style of title bar, iconify and close buttons similar to Window Maker. + * Window menu, with basic window actions (not required to adapt to state). + +* Multiple workspaces, based on toolkit. + * Navigate via keys (ctrl-window-alt-arrows, hardcoded). + +* Dock, visible across workspaces, based on toolkit. + * Style similar to Window Maker. + * With application launchers (hardcoded). + +* Clip, based on toolkit. + * Display the current workspace. + * Buttons to switch between workspaces. + +* Application launchers, based on toolkit. + * Display an icon. + * Display application status (*starting*, *running*). + * Configurable (in code). + +* Window actions, based on toolkit. + * Move ([done] drag via title bar, or [pending] window-alt-click) + * [done] Resize windows, including a resize bar. + * [done] Fullscreen windows. + * [done] Maximize windows. + * Minimize (*iconify*) windows. + * Roll up (*shade*) windows. + * Raise window when activated. + +* Visualization of iconified applications, based on toolkit. + +* Task list (window-alt-esc), cycling through windows, based on toolkit. + +### Internals and code organization + +* [done] Design a toolkit and re-factor the codebase to make use of it. + * Ensure the main features (eg. all explicit actions and features above) are + tested. + ## Pending Features for further versions, not ordered by priority nor timeline. +* Wayland protocol adherence. + * Support XDG `wm_capabilities` and advertise the compositor features. + * Fullscreen: Hide all other visuals when a window takes fullscreen. + * XWayland support (X11 clients). * Dock Apps. @@ -100,6 +164,8 @@ Features for further versions, not ordered by priority nor timeline. * Determine how to detect client preferences. * Configurable and overridable (titlebar, resizebar, buttons, ...). * Scaling factor per application. + * Build and test a clear model for `organic`/`maximized`/`fullscreen` state + switches and precedence. * Application support. * Icons retrieved and used for iconified windows. See [themes](https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html). diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1e89d140..91a1fada 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,15 +32,14 @@ SET(SOURCES menu.c menu_item.c output.c - resizebar.c server.c subprocess_monitor.c task_list.c tile.c tile_container.c - titlebar.c - util.c view.c + wlmtk_xdg_popup.c + wlmtk_xdg_toplevel.c workspace.c xdg_decoration.c xdg_popup.c @@ -66,15 +65,14 @@ SET(HEADERS menu.h menu_item.h output.h - resizebar.h server.h subprocess_monitor.h task_list.h tile_container.h tile.h - titlebar.h - util.h view.h + wlmtk_xdg_popup.h + wlmtk_xdg_toplevel.h workspace.h xdg_decoration.h xdg_popup.h @@ -83,7 +81,7 @@ SET(HEADERS ) ADD_EXECUTABLE(wlmaker wlmaker.c ${SOURCES} ${HEADERS}) -ADD_DEPENDENCIES(wlmaker protocol_headers decorations toolkit) +ADD_DEPENDENCIES(wlmaker protocol_headers toolkit) TARGET_COMPILE_DEFINITIONS( wlmaker PRIVATE WLMAKER_ICON_DATA_DIR="${CMAKE_INSTALL_FULL_DATAROOTDIR}/icons/wlmaker") @@ -110,7 +108,7 @@ TARGET_INCLUDE_DIRECTORIES( TARGET_LINK_LIBRARIES( wlmaker PRIVATE base - decorations + toolkit wlmaker_protocols PkgConfig::CAIRO PkgConfig::LIBDRM @@ -121,7 +119,7 @@ TARGET_LINK_LIBRARIES( ) ADD_EXECUTABLE(wlmaker_test wlmaker_test.c ${SOURCES} ${HEADERS}) -ADD_DEPENDENCIES(wlmaker_test protocol_headers decorations toolkit) +ADD_DEPENDENCIES(wlmaker_test protocol_headers toolkit) TARGET_INCLUDE_DIRECTORIES( wlmaker_test PRIVATE ${PROJECT_BINARY_DIR}/third_party/protocols @@ -137,7 +135,7 @@ TARGET_INCLUDE_DIRECTORIES( TARGET_LINK_LIBRARIES( wlmaker_test PRIVATE base - decorations + toolkit wlmaker_protocols PkgConfig::CAIRO PkgConfig::LIBDRM diff --git a/src/button.c b/src/button.c index a9d33153..b83f3175 100644 --- a/src/button.c +++ b/src/button.c @@ -25,7 +25,7 @@ #define WLR_USE_UNSTABLE #include -#include +#include #undef WLR_USE_UNSTABLE /* == Definitions ========================================================== */ @@ -215,10 +215,10 @@ void _button_enter(wlmaker_interactive_t *interactive_ptr) wlmaker_button_t *button_ptr = button_from_interactive(interactive_ptr); if (button_ptr->activated) button_press(button_ptr, true); - wlr_xcursor_manager_set_cursor_image( + wlr_cursor_set_xcursor( + interactive_ptr->cursor_ptr->wlr_cursor_ptr, interactive_ptr->cursor_ptr->wlr_xcursor_manager_ptr, - "left_ptr", - interactive_ptr->cursor_ptr->wlr_cursor_ptr); + "left_ptr"); } /* ------------------------------------------------------------------------- */ diff --git a/src/clip.c b/src/clip.c index acfcf688..c7996e73 100644 --- a/src/clip.c +++ b/src/clip.c @@ -23,7 +23,7 @@ #include "button.h" #include "config.h" #include "decorations.h" -#include "util.h" +#include "toolkit/toolkit.h" /* == Declarations ========================================================= */ @@ -250,7 +250,7 @@ wlmaker_clip_t *wlmaker_clip_create( clip_ptr->view.anchor = WLMAKER_VIEW_ANCHOR_BOTTOM | WLMAKER_VIEW_ANCHOR_RIGHT; - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->workspace_changed, &clip_ptr->workspace_changed_listener, handle_workspace_changed); diff --git a/src/config.c b/src/config.c index 890d5b8f..e16b654f 100644 --- a/src/config.c +++ b/src/config.c @@ -72,31 +72,17 @@ const wlmaker_config_theme_t wlmaker_config_theme = { .window_margin_color = 0xff000000, // Pich black, opaque. .window_margin_width = 1, - .titlebar_focussed_fill = { - .type = WLMAKER_STYLE_COLOR_HGRADIENT, - .param = { .hgradient = { .from = 0xff505a5e,.to = 0xff202a2e }} - }, - .titlebar_focussed_text_color = 0xffffffff, - .titlebar_blurred_fill = { - .type = WLMAKER_STYLE_COLOR_HGRADIENT, - .param = { .hgradient = { .from = 0xffc2c0c5,.to = 0xff828085 }} - }, - .titlebar_blurred_text_color = 0xff000000, - .resizebar_fill = { - .type = WLMAKER_STYLE_COLOR_SOLID, - .param = { .solid = { .color = 0xffc2c0c5 }} - }, .tile_fill = { - .type = WLMAKER_STYLE_COLOR_DGRADIENT, + .type = WLMTK_STYLE_COLOR_DGRADIENT, .param = { .hgradient = { .from = 0xffa6a6b6,.to = 0xff515561 }} }, .iconified_title_fill = { - .type = WLMAKER_STYLE_COLOR_SOLID, + .type = WLMTK_STYLE_COLOR_SOLID, .param = { .solid = { .color = 0xff404040 }} }, .iconified_title_color = 0xffffffff, // White. .menu_fill = { - .type = WLMAKER_STYLE_COLOR_HGRADIENT, + .type = WLMTK_STYLE_COLOR_HGRADIENT, .param = { .hgradient = { .from = 0xffc2c0c5, .to = 0xff828085 }} }, .menu_margin_color = 0xff000000, // Pitch black, opaque. @@ -104,18 +90,18 @@ const wlmaker_config_theme_t wlmaker_config_theme = { .menu_padding_width = 1, .menu_item_enabled_fill = { - .type = WLMAKER_STYLE_COLOR_SOLID, + .type = WLMTK_STYLE_COLOR_SOLID, .param = { .solid = { .color = 0x00000000 }} // Transparent. }, .menu_item_enabled_text_color = 0xff000000, // Black, opaque. .menu_item_selected_fill = { - .type = WLMAKER_STYLE_COLOR_SOLID, + .type = WLMTK_STYLE_COLOR_SOLID, .param = { .solid = { .color = 0xffffffff }} // White, opaque.. }, .menu_item_selected_text_color = 0xff000000, // Black, opaque. .task_list_fill = { - .type = WLMAKER_STYLE_COLOR_SOLID, + .type = WLMTK_STYLE_COLOR_SOLID, .param.solid.color = 0xc0202020 // Dark grey, partly transparent. }, .task_list_text_color = 0xffffffff, diff --git a/src/config.h b/src/config.h index 0b70c9c6..b75d82ad 100644 --- a/src/config.h +++ b/src/config.h @@ -38,9 +38,9 @@ typedef enum { WLMAKER_CONFIG_DECORATION_SUGGEST_CLIENT, /** Mode NONE will be set to SERVER; but other modes left unchanged. */ WLMAKER_CONFIG_DECORATION_SUGGEST_SERVER, - /** Will set all windows to CLIENT, no mather what they requested. */ + /** Will set all windows to CLIENT, no matter what they requested. */ WLMAKER_CONFIG_DECORATION_ENFORCE_CLIENT, - /** Will set all windows to SERVER, no mather what they requested. */ + /** Will set all windows to SERVER, no matter what they requested. */ WLMAKER_CONFIG_DECORATION_ENFORCE_SERVER } wlmaker_config_decoration_t; @@ -51,27 +51,15 @@ typedef struct { /** Width of the window margin, in pixels. */ uint32_t window_margin_width; - /** Color of the title text when focussed. */ - uint32_t titlebar_focussed_text_color; - /** Color of the title text when blurred. */ - uint32_t titlebar_blurred_text_color; - - /** Fill style of the title bar, when focussed. Including buttons. */ - wlmaker_style_fill_t titlebar_focussed_fill; - /** Fill style of the title bar, when blurred. Including buttons. */ - wlmaker_style_fill_t titlebar_blurred_fill; - - /** Fill style of the resize bar. */ - wlmaker_style_fill_t resizebar_fill; /** Fill style of a tile. */ - wlmaker_style_fill_t tile_fill; + wlmtk_style_fill_t tile_fill; /** File style of the title element of an iconified. */ - wlmaker_style_fill_t iconified_title_fill; + wlmtk_style_fill_t iconified_title_fill; /** Color of the iconified's title. */ uint32_t iconified_title_color; /** Fill style of the menu's background. */ - wlmaker_style_fill_t menu_fill; + wlmtk_style_fill_t menu_fill; /** Color of the menu's margin and padding. */ uint32_t menu_margin_color; /** Width of the menu's margin. */ @@ -80,16 +68,16 @@ typedef struct { uint32_t menu_padding_width; /** Fill style of a menu item when enabled. */ - wlmaker_style_fill_t menu_item_enabled_fill; + wlmtk_style_fill_t menu_item_enabled_fill; /** Text color of menu item when enabled. */ uint32_t menu_item_enabled_text_color; /** Fill style of a menu item when selected. */ - wlmaker_style_fill_t menu_item_selected_fill; + wlmtk_style_fill_t menu_item_selected_fill; /** Text color of menu item when selected. */ uint32_t menu_item_selected_text_color; /** Fill style of the task list. */ - wlmaker_style_fill_t task_list_fill; + wlmtk_style_fill_t task_list_fill; /** Color of the text describing tasks in the task list. */ uint32_t task_list_text_color; } wlmaker_config_theme_t; diff --git a/src/cursor.c b/src/cursor.c index 9e40fce6..e1905270 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -21,13 +21,14 @@ #include "cursor.h" #include "config.h" -#include "util.h" +#include "toolkit/toolkit.h" #include #define WLR_USE_UNSTABLE #include #include +#include #include #undef WLR_USE_UNSTABLE @@ -106,29 +107,28 @@ wlmaker_cursor_t *wlmaker_cursor_create(wlmaker_server_t *server_ptr) // // https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html - // TODO: Need a mode for 'normal', 'move', 'resize'. - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &cursor_ptr->wlr_cursor_ptr->events.motion, &cursor_ptr->motion_listener, handle_motion); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &cursor_ptr->wlr_cursor_ptr->events.motion_absolute, &cursor_ptr->motion_absolute_listener, handle_motion_absolute); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &cursor_ptr->wlr_cursor_ptr->events.button, &cursor_ptr->button_listener, handle_button); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &cursor_ptr->wlr_cursor_ptr->events.axis, &cursor_ptr->axis_listener, handle_axis); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &cursor_ptr->wlr_cursor_ptr->events.frame, &cursor_ptr->frame_listener, handle_frame); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &cursor_ptr->server_ptr->wlr_seat_ptr->events.request_set_cursor, &cursor_ptr->seat_request_set_cursor_listener, handle_seat_request_set_cursor); @@ -163,71 +163,6 @@ void wlmaker_cursor_attach_input_device( wlr_input_device_ptr); } -/* ------------------------------------------------------------------------- */ -void wlmaker_cursor_begin_move( - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr) -{ - if (view_ptr != wlmaker_workspace_get_activated_view( - wlmaker_server_get_current_workspace(cursor_ptr->server_ptr))) { - bs_log(BS_WARNING, "Denying move request from non-activated view."); - return; - } - - cursor_ptr->grabbed_view_ptr = view_ptr; - int x, y; - wlmaker_view_get_position(cursor_ptr->grabbed_view_ptr, &x, &y); - // TODO(kaeser@gubbe.ch): Inconsistent to have separate meaning of grab_x - // and grab_y for MOVE vs RESIZE. Should be cleaned up. - cursor_ptr->grab_x = cursor_ptr->wlr_cursor_ptr->x - x; - cursor_ptr->grab_y = cursor_ptr->wlr_cursor_ptr->y - y; - cursor_ptr->mode = WLMAKER_CURSOR_MOVE; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_cursor_begin_resize( - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr, - uint32_t edges) -{ - if (view_ptr != wlmaker_workspace_get_activated_view( - wlmaker_server_get_current_workspace(cursor_ptr->server_ptr))) { - bs_log(BS_WARNING, "Denying resize request from non-activated view."); - return; - } - - cursor_ptr->grabbed_view_ptr = view_ptr; - cursor_ptr->grab_x = cursor_ptr->wlr_cursor_ptr->x; - cursor_ptr->grab_y = cursor_ptr->wlr_cursor_ptr->y; - cursor_ptr->mode = WLMAKER_CURSOR_RESIZE; - - uint32_t width, height; - wlmaker_view_get_size(view_ptr, &width, &height); - cursor_ptr->grabbed_geobox.width = width; - cursor_ptr->grabbed_geobox.height = height; - wlmaker_view_get_position(view_ptr, - &cursor_ptr->grabbed_geobox.x, - &cursor_ptr->grabbed_geobox.y); - cursor_ptr->resize_edges = edges; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_cursor_unmap_view( - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr) -{ - if (cursor_ptr->grabbed_view_ptr == view_ptr) { - cursor_ptr->grabbed_view_ptr = NULL; - cursor_ptr->mode = WLMAKER_CURSOR_PASSTHROUGH; - } - - if (cursor_ptr->under_cursor_view_ptr == view_ptr) { - // TODO(kaeser@gubbe.ch): Should eavluate which of the view is now - // below the cursor and update "pointer focus" accordingly. - update_under_cursor_view(cursor_ptr, NULL); - } -} - /* ------------------------------------------------------------------------- */ void wlmaker_cursor_get_position( const wlmaker_cursor_t *cursor_ptr, @@ -305,37 +240,14 @@ void handle_button(struct wl_listener *listener_ptr, listener_ptr, cursor_ptr, button_listener); struct wlr_pointer_button_event *wlr_pointer_button_event_ptr = data_ptr; - struct wlr_keyboard *wlr_keyboard_ptr = wlr_seat_get_keyboard( - cursor_ptr->server_ptr->wlr_seat_ptr); - if (NULL != wlr_keyboard_ptr) { - uint32_t modifiers = wlr_keyboard_get_modifiers(wlr_keyboard_ptr); - if (wlmaker_config_window_drag_modifiers != 0 && - wlmaker_config_window_drag_modifiers == modifiers && - wlr_pointer_button_event_ptr->state == WLR_BUTTON_PRESSED) { - struct wlr_surface *wlr_surface_ptr; - double rel_x, rel_y; - wlmaker_view_t *view_ptr = wlmaker_view_at( - cursor_ptr->server_ptr, - cursor_ptr->wlr_cursor_ptr->x, - cursor_ptr->wlr_cursor_ptr->y, - &wlr_surface_ptr, - &rel_x, - &rel_y); - if (NULL != view_ptr) { - wlmaker_workspace_raise_view( - wlmaker_server_get_current_workspace( - cursor_ptr->server_ptr), - view_ptr); - wlmaker_workspace_activate_view( - wlmaker_server_get_current_workspace( - cursor_ptr->server_ptr), - view_ptr); - update_under_cursor_view(cursor_ptr, view_ptr); - wlmaker_cursor_begin_move(cursor_ptr, view_ptr); - return; - } - } - } + bool consumed = wlmtk_workspace_button( + wlmaker_workspace_wlmtk(wlmaker_server_get_current_workspace( + cursor_ptr->server_ptr)), + wlr_pointer_button_event_ptr); + + // TODO(kaeser@gubbe.ch): The code below is for the pre-toolkit version. + // Remove it, once we're fully on toolkit. + if (consumed) return; // Notify the client with pointer focus that a button press has occurred. wlr_seat_pointer_notify_button( @@ -365,7 +277,6 @@ void handle_button(struct wl_listener *listener_ptr, if (wlr_pointer_button_event_ptr->state == WLR_BUTTON_RELEASED) { wl_signal_emit(&cursor_ptr->button_release_event, data_ptr); - cursor_ptr->mode = WLMAKER_CURSOR_PASSTHROUGH; } } @@ -473,93 +384,12 @@ void handle_seat_request_set_cursor( */ void process_motion(wlmaker_cursor_t *cursor_ptr, uint32_t time_msec) { - if (cursor_ptr->mode == WLMAKER_CURSOR_MOVE) { - wlmaker_view_set_position( - cursor_ptr->grabbed_view_ptr, - cursor_ptr->wlr_cursor_ptr->x - cursor_ptr->grab_x, - cursor_ptr->wlr_cursor_ptr->y - cursor_ptr->grab_y); - return; - } else if (cursor_ptr->mode == WLMAKER_CURSOR_RESIZE) { - - // The geometry describes the overall shell geometry *relative* to the - // node position. This may eg. include client-side decoration, that - // may be placed in an extra surface above the nominal window (and - // node). - // - // Thus the position and dimensions of the visible area is given by - // the geobox position (relative to the node position) and with x height. - - double x = cursor_ptr->wlr_cursor_ptr->x - cursor_ptr->grab_x; - double y = cursor_ptr->wlr_cursor_ptr->y - cursor_ptr->grab_y; - - // Update new boundaries by the relative movement. - int top = cursor_ptr->grabbed_geobox.y; - int bottom = cursor_ptr->grabbed_geobox.y + cursor_ptr->grabbed_geobox.height; - if (cursor_ptr->resize_edges & WLR_EDGE_TOP) { - top += y; - if (top >= bottom) top = bottom - 1; - } else if (cursor_ptr->resize_edges & WLR_EDGE_BOTTOM) { - bottom += y; - if (bottom <= top) bottom = top + 1; - } - - int left = cursor_ptr->grabbed_geobox.x; - int right = cursor_ptr->grabbed_geobox.x + cursor_ptr->grabbed_geobox.width; - if (cursor_ptr->resize_edges & WLR_EDGE_LEFT) { - left += x; - if (left >= right) left = right - 1 ; - } else if (cursor_ptr->resize_edges & WLR_EDGE_RIGHT) { - right += x; - if (right <= left) right = left + 1; - } - - wlmaker_view_set_position(cursor_ptr->grabbed_view_ptr, left, top); - wlmaker_view_set_size(cursor_ptr->grabbed_view_ptr, - right - left, bottom - top); - return; - } - - double rel_x, rel_y; - struct wlr_surface *wlr_surface_ptr = NULL; - wlmaker_view_t *view_ptr = wlmaker_view_at( - cursor_ptr->server_ptr, + wlmtk_workspace_motion( + wlmaker_workspace_wlmtk(wlmaker_server_get_current_workspace( + cursor_ptr->server_ptr)), cursor_ptr->wlr_cursor_ptr->x, cursor_ptr->wlr_cursor_ptr->y, - &wlr_surface_ptr, - &rel_x, - &rel_y); - update_under_cursor_view(cursor_ptr, view_ptr); - if (NULL == view_ptr) { - wlr_xcursor_manager_set_cursor_image( - cursor_ptr->wlr_xcursor_manager_ptr, - "left_ptr", - cursor_ptr->wlr_cursor_ptr); - } else { - wlmaker_view_handle_motion( - view_ptr, - cursor_ptr->wlr_cursor_ptr->x, - cursor_ptr->wlr_cursor_ptr->y); - } - - if (NULL == wlr_surface_ptr) { - // Clear pointer focus, so that future button events are no longer sent - // to the surface that had the focus. - wlr_seat_pointer_clear_focus(cursor_ptr->server_ptr->wlr_seat_ptr); - - } else { - // The notify_enter() function gives the pointer focus to the specified - // surface. The seat will send future button events there. - wlr_seat_pointer_notify_enter( - cursor_ptr->server_ptr->wlr_seat_ptr, - wlr_surface_ptr, - rel_x, - rel_y); - wlr_seat_pointer_notify_motion( - cursor_ptr->server_ptr->wlr_seat_ptr, - time_msec, - rel_x, - rel_y); - } + time_msec); } /* ------------------------------------------------------------------------- */ diff --git a/src/cursor.h b/src/cursor.h index 7ca85439..3d658e08 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -30,16 +30,6 @@ typedef struct _wlmaker_cursor_t wlmaker_cursor_t; extern "C" { #endif // __cplusplus -/** Mode of the cursor. */ -typedef enum { - /** Cursor movements are passed on to the client. */ - WLMAKER_CURSOR_PASSTHROUGH, - /** View-move mode. Movements kept and used to propel the view. */ - WLMAKER_CURSOR_MOVE, - /** Resize mode. Movements kept and used to resize the view. */ - WLMAKER_CURSOR_RESIZE, -} wlmaker_cursor_mode_t; - /** State and tools for handling wlmaker cursors. */ struct _wlmaker_cursor_t { /** Back-link to wlmaker_server_t. */ @@ -64,27 +54,8 @@ struct _wlmaker_cursor_t { /** Listener for the `request_set_cursor` event of `wlr_seat`. */ struct wl_listener seat_request_set_cursor_listener; - /** Mode that the cursor is in, eg. moving or resizing. */ - wlmaker_cursor_mode_t mode; - /** The currently grabbed view, when in "move" mode. */ - wlmaker_view_t *grabbed_view_ptr; /** The view that is currently active and under the cursor. */ wlmaker_view_t *under_cursor_view_ptr; - /** - * Horizontal position of when the move was activated, relative to the - * grabbed view. - */ - double grab_x; - /** - * Vertical position of when the move was activated, relative to the - * grabbed view. - */ - double grab_y; - /** Geometry at the time the move was initiated. */ - struct wlr_box grabbed_geobox; - /** Edges to resize along. */ - uint32_t resize_edges; - /** wlmaker internal: catch 'release' events of cursors. */ struct wl_signal button_release_event; @@ -116,39 +87,6 @@ void wlmaker_cursor_attach_input_device( wlmaker_cursor_t *cursor_ptr, struct wlr_input_device *wlr_input_device_ptr); -/** - * Begins a "move" interaction for the given view. - * - * @param cursor_ptr - * @param view_ptr - */ -void wlmaker_cursor_begin_move( - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr); - -/** - * Begins a "resize" interaction for the given view. - * - * @param cursor_ptr - * @param view_ptr - * @param edges - */ -void wlmaker_cursor_begin_resize( - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr, - uint32_t edges); - -/** - * Reports |view_ptr| as unmapped. Removes it from the set of views that can - * be called back, etc. - * - * @param cursor_ptr - * @param view_ptr - */ -void wlmaker_cursor_unmap_view( - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr); - /** * Retrieves the current pointer's position into |*x_ptr|, |*y_ptr|. * diff --git a/src/decorations.c b/src/decorations.c index 1a3fe70f..56be9fd7 100644 --- a/src/decorations.c +++ b/src/decorations.c @@ -39,7 +39,7 @@ const uint32_t wlmaker_decorations_clip_button_size = 22; static cairo_surface_t *create_background( unsigned width, unsigned height, - const wlmaker_style_fill_t *fill_ptr); + const wlmtk_style_fill_t *fill_ptr); /** Lookup paths for icons. */ const char *lookup_paths[] = { @@ -57,7 +57,7 @@ const char *lookup_paths[] = { /* ------------------------------------------------------------------------- */ void wlmaker_decorations_draw_tile( cairo_t *cairo_ptr, - const wlmaker_style_fill_t *fill_ptr, + const wlmtk_style_fill_t *fill_ptr, bool pressed) { wlmaker_primitives_cairo_fill(cairo_ptr, fill_ptr); @@ -112,7 +112,7 @@ bool wlmaker_decorations_draw_tile_icon( /* ------------------------------------------------------------------------- */ void wlmaker_decorations_draw_iconified( cairo_t *cairo_ptr, - const wlmaker_style_fill_t *fill_ptr, + const wlmtk_style_fill_t *fill_ptr, uint32_t font_color, const char *title_ptr) { @@ -144,7 +144,7 @@ void wlmaker_decorations_draw_iconified( /* ------------------------------------------------------------------------- */ bool wlmaker_decorations_draw_clip( cairo_t *cairo_ptr, - const wlmaker_style_fill_t *fill_ptr, + const wlmtk_style_fill_t *fill_ptr, bool pressed) { // For readability. @@ -227,7 +227,7 @@ bool wlmaker_decorations_draw_clip( /* ------------------------------------------------------------------------- */ bool wlmaker_decorations_draw_clip_button_next( cairo_t *cairo_ptr, - const wlmaker_style_fill_t *fill_ptr, + const wlmtk_style_fill_t *fill_ptr, bool pressed) { BS_ASSERT((int)wlmaker_decorations_clip_button_size == @@ -322,7 +322,7 @@ bool wlmaker_decorations_draw_clip_button_next( /* ------------------------------------------------------------------------- */ bool wlmaker_decorations_draw_clip_button_prev( cairo_t *cairo_ptr, - const wlmaker_style_fill_t *fill_ptr, + const wlmtk_style_fill_t *fill_ptr, bool pressed) { BS_ASSERT((int)wlmaker_decorations_clip_button_size == @@ -429,7 +429,7 @@ bool wlmaker_decorations_draw_clip_button_prev( static cairo_surface_t *create_background( unsigned width, unsigned height, - const wlmaker_style_fill_t *fill_ptr) + const wlmtk_style_fill_t *fill_ptr) { cairo_surface_t *surface_ptr = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, width, height); @@ -477,8 +477,8 @@ void test_tile(bs_test_t *test_ptr) { } cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, cairo_ptr); - wlmaker_style_fill_t fill = { - .type = WLMAKER_STYLE_COLOR_DGRADIENT, + wlmtk_style_fill_t fill = { + .type = WLMTK_STYLE_COLOR_DGRADIENT, .param = { .hgradient = { .from = 0xffa6a6b6,.to = 0xff515561 }} }; wlmaker_decorations_draw_tile(cairo_ptr, &fill, false); @@ -497,8 +497,8 @@ void test_iconified(bs_test_t *test_ptr) { } cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, cairo_ptr); - wlmaker_style_fill_t fill = { - .type = WLMAKER_STYLE_COLOR_SOLID, + wlmtk_style_fill_t fill = { + .type = WLMTK_STYLE_COLOR_SOLID, .param = { .solid = { .color = 0xff808080 }} }; wlmaker_decorations_draw_iconified(cairo_ptr, &fill, 0xffffffff, "Title"); @@ -516,8 +516,8 @@ void test_clip(bs_test_t *test_ptr) { return; } cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); - wlmaker_style_fill_t fill = { - .type = WLMAKER_STYLE_COLOR_DGRADIENT, + wlmtk_style_fill_t fill = { + .type = WLMTK_STYLE_COLOR_DGRADIENT, .param = { .hgradient = { .from = 0xffa6a6b6,.to = 0xff515561 }} }; BS_TEST_VERIFY_NEQ(test_ptr, NULL, cairo_ptr); @@ -542,8 +542,8 @@ void test_clip_button_next(bs_test_t *test_ptr) { return; } cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); - wlmaker_style_fill_t fill = { - .type = WLMAKER_STYLE_COLOR_DGRADIENT, + wlmtk_style_fill_t fill = { + .type = WLMTK_STYLE_COLOR_DGRADIENT, .param = { .hgradient = { .from = 0xffa6a6b6,.to = 0xff515561 }} }; BS_TEST_VERIFY_NEQ(test_ptr, NULL, cairo_ptr); @@ -570,8 +570,8 @@ void test_clip_button_prev(bs_test_t *test_ptr) { return; } cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); - wlmaker_style_fill_t fill = { - .type = WLMAKER_STYLE_COLOR_DGRADIENT, + wlmtk_style_fill_t fill = { + .type = WLMTK_STYLE_COLOR_DGRADIENT, .param = { .hgradient = { .from = 0xffa6a6b6,.to = 0xff515561 }} }; BS_TEST_VERIFY_NEQ(test_ptr, NULL, cairo_ptr); diff --git a/src/decorations.h b/src/decorations.h index cb58ca18..b11dd389 100644 --- a/src/decorations.h +++ b/src/decorations.h @@ -39,32 +39,6 @@ extern const uint32_t wlmaker_decorations_tile_size; /** Size of the clip button (length of the catheti) */ extern const uint32_t wlmaker_decorations_clip_button_size; -/** - * Creates a cairo image surface for the background of the title bar. - * - * @param width Full with of the title bar. The width depends on - * the window size; the height of the title bar is - * hardcoded. - * @param fill_ptr Specification for the fill. - * - * @return a `cairo_surface_t` image target, filled as specificed. - */ -cairo_surface_t *wlmaker_decorations_titlebar_create_background( - uint32_t width, const wlmaker_style_fill_t *fill_ptr); - -/** - * Creates a cairo image surface for the background of the resize bar. - * - * @param width Full with of the resize bar. The width depends on - * the window size; the height of the reizse bar is - * hardcoded. - * @param fill_ptr Specification for the fill. - * - * @return a `cairo_surface_t` image target, filled as specificed. - */ -cairo_surface_t *wlmaker_decorations_resizebar_create_background( - uint32_t width, const wlmaker_style_fill_t *fill_ptr); - /** * Draws a tile into the `cairo_t`. * @@ -74,7 +48,7 @@ cairo_surface_t *wlmaker_decorations_resizebar_create_background( */ void wlmaker_decorations_draw_tile( cairo_t *cairo_ptr, - const wlmaker_style_fill_t *fill_ptr, + const wlmtk_style_fill_t *fill_ptr, bool pressed); /** @@ -99,7 +73,7 @@ bool wlmaker_decorations_draw_tile_icon( */ void wlmaker_decorations_draw_iconified( cairo_t *cairo_ptr, - const wlmaker_style_fill_t *fill_ptr, + const wlmtk_style_fill_t *fill_ptr, uint32_t font_color, const char *title_ptr); @@ -117,7 +91,7 @@ void wlmaker_decorations_draw_iconified( */ bool wlmaker_decorations_draw_clip( cairo_t *cairo_ptr, - const wlmaker_style_fill_t *fill_ptr, + const wlmtk_style_fill_t *fill_ptr, bool pressed); /** @@ -131,7 +105,7 @@ bool wlmaker_decorations_draw_clip( */ bool wlmaker_decorations_draw_clip_button_next( cairo_t *cairo_ptr, - const wlmaker_style_fill_t *fill_ptr, + const wlmtk_style_fill_t *fill_ptr, bool pressed); /** @@ -145,7 +119,7 @@ bool wlmaker_decorations_draw_clip_button_next( */ bool wlmaker_decorations_draw_clip_button_prev( cairo_t *cairo_ptr, - const wlmaker_style_fill_t *fill_ptr, + const wlmtk_style_fill_t *fill_ptr, bool pressed); /** Unit tests. */ diff --git a/src/decorations/CMakeLists.txt b/src/decorations/CMakeLists.txt deleted file mode 100644 index f098a798..00000000 --- a/src/decorations/CMakeLists.txt +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -CMAKE_MINIMUM_REQUIRED(VERSION 3.13) - -ADD_LIBRARY(decorations - element.c - margin.c - resizebar.c - titlebar.c - window_decorations.c - element.h - margin.h - resizebar.h - titlebar.h - window_decorations.h) - -TARGET_COMPILE_OPTIONS( - decorations PRIVATE - ${WAYLAND_CFLAGS} - ${WAYLAND_CFLAGS_OTHER} -) - -TARGET_INCLUDE_DIRECTORIES( - decorations PRIVATE - ${CAIRO_INCLUDE_DIRS} - # TODO(kaeser@gubbe.ch): These should not be required. - ${PROJECT_BINARY_DIR}/third_party/protocols - ${PIXMAN_INCLUDE_DIRS} -) - -TARGET_LINK_LIBRARIES( - decorations base toolkit) diff --git a/src/decorations/element.c b/src/decorations/element.c deleted file mode 100644 index 2972e84a..00000000 --- a/src/decorations/element.c +++ /dev/null @@ -1,449 +0,0 @@ -/* ========================================================================= */ -/** - * @file element.c - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "element.h" - -#include - -#include "../button.h" -#include "../resizebar.h" -#include "../titlebar.h" - -/* == Declarations ========================================================= */ - -/** Decorations element. */ -struct _wlmaker_decorations_element_t { - /** Scene graph node holding the element. */ - struct wlr_scene_buffer *wlr_scene_buffer_ptr; - - /** - * Interactive for the element. To be created by instantiated element, will - * be destroyed in @ref wlmaker_decorations_element_fini. - */ - wlmaker_interactive_t *interactive_ptr; - - /** - * Margin of the element, or NULL. - * - * TODO(kaeser@gubbe.ch): Consider moving this to the "container". - */ - wlmaker_decorations_margin_t *margin_ptr; -}; - -/** State of a button. */ -struct _wlmaker_decorations_button_t { - /** The base element. */ - wlmaker_decorations_element_t element; - /** Back-link. */ - wlmaker_view_t *view_ptr; -}; - -/** State of a title. */ -struct _wlmaker_decorations_title_t { - /** The base element. */ - wlmaker_decorations_element_t element; - /** Back-link. */ - wlmaker_view_t *view_ptr; -}; - -/** State of a resize. */ -struct _wlmaker_decorations_resize_t { - /** The base element. */ - wlmaker_decorations_element_t element; - /** Back-link. */ - wlmaker_view_t *view_ptr; -}; - -/* == Exported methods ===================================================== */ - -/* ------------------------------------------------------------------------- */ -bool wlmaker_decorations_element_init( - wlmaker_decorations_element_t *element_ptr, - struct wlr_scene_tree *wlr_scene_tree_ptr, - void *data_ptr, - unsigned width, - unsigned height, - uint32_t edges) -{ - BS_ASSERT(NULL == element_ptr->wlr_scene_buffer_ptr); - - element_ptr->wlr_scene_buffer_ptr = wlr_scene_buffer_create( - wlr_scene_tree_ptr, NULL); - if (NULL == element_ptr->wlr_scene_buffer_ptr) { - wlmaker_decorations_element_fini(element_ptr); - return false; - } - element_ptr->wlr_scene_buffer_ptr->node.data = data_ptr; - - if (0 != edges) { - element_ptr->margin_ptr = wlmaker_decorations_margin_create( - wlr_scene_tree_ptr, - 0, 0, width, height, // element_ptr->width, element_ptr->height, - edges); - } - - return true; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_element_fini( - wlmaker_decorations_element_t *element_ptr) -{ - if (NULL != element_ptr->margin_ptr) { - wlmaker_decorations_margin_destroy(element_ptr->margin_ptr); - element_ptr->margin_ptr = NULL; - } - - if (NULL != element_ptr->interactive_ptr) { - wlmaker_interactive_node_destroy( - &element_ptr->interactive_ptr->avlnode); - element_ptr->interactive_ptr = NULL; - } - - if (NULL != element_ptr->wlr_scene_buffer_ptr) { - wlr_scene_node_destroy(&element_ptr->wlr_scene_buffer_ptr->node); - element_ptr->wlr_scene_buffer_ptr = NULL; - } -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_element_set_position( - wlmaker_decorations_element_t *element_ptr, - int x, - int y) -{ - wlr_scene_node_set_position( - &element_ptr->wlr_scene_buffer_ptr->node, x, y); - if (NULL != element_ptr->margin_ptr) { - wlmaker_decorations_margin_set_position( - element_ptr->margin_ptr, x, y); - } -} - -/* ------------------------------------------------------------------------- */ -wlmaker_decorations_button_t *wlmaker_decorations_button_create( - struct wlr_scene_tree *wlr_scene_tree_ptr, - wlmaker_cursor_t *cursor_ptr, - wlmaker_interactive_callback_t callback, - wlmaker_view_t *view_ptr, - struct wlr_buffer *button_released_ptr, - struct wlr_buffer *button_pressed_ptr, - struct wlr_buffer *button_blurred_ptr, - uint32_t edges) -{ - BS_ASSERT(button_released_ptr->width == button_pressed_ptr->width); - BS_ASSERT(button_released_ptr->width == button_blurred_ptr->width); - BS_ASSERT(button_released_ptr->height == button_pressed_ptr->height); - BS_ASSERT(button_released_ptr->height == button_blurred_ptr->height); - - wlmaker_decorations_button_t *button_ptr = logged_calloc( - 1, sizeof(wlmaker_decorations_button_t)); - if (NULL == button_ptr) return NULL; - if (!wlmaker_decorations_element_init( - &button_ptr->element, - wlr_scene_tree_ptr, - view_ptr, - button_released_ptr->width, - button_released_ptr->height, - edges)) { - wlmaker_decorations_button_destroy(button_ptr); - return NULL; - } - - button_ptr->element.interactive_ptr = wlmaker_button_create( - button_ptr->element.wlr_scene_buffer_ptr, - cursor_ptr, - callback, - view_ptr, - button_released_ptr, - button_pressed_ptr, - button_blurred_ptr); - if (NULL == button_ptr->element.interactive_ptr) { - wlmaker_decorations_button_destroy(button_ptr); - return NULL; - } - - wlmaker_interactive_focus( - button_ptr->element.interactive_ptr, - view_ptr->active); - - if (!bs_avltree_insert( - view_ptr->interactive_tree_ptr, - &button_ptr->element.interactive_ptr->wlr_scene_buffer_ptr->node, - &button_ptr->element.interactive_ptr->avlnode, - false)) { - bs_log(BS_ERROR, "Unexpected: Fail to insert into tree."); - wlmaker_decorations_button_destroy(button_ptr); - return NULL; - } - button_ptr->view_ptr = view_ptr; - - return button_ptr; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_button_destroy( - wlmaker_decorations_button_t *button_ptr) -{ - if (NULL != button_ptr->view_ptr) { - bs_avltree_delete( - button_ptr->view_ptr->interactive_tree_ptr, - &button_ptr->element.interactive_ptr->wlr_scene_buffer_ptr->node); - button_ptr->view_ptr = NULL; - } - - // The interactive is deleted in element_fini(). - - wlmaker_decorations_element_fini(&button_ptr->element); - free(button_ptr); -} - -/* ------------------------------------------------------------------------- */ -wlmaker_decorations_element_t *wlmaker_decorations_element_from_button( - wlmaker_decorations_button_t *button_ptr) -{ - return &button_ptr->element; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_button_set_textures( - wlmaker_decorations_button_t *button_ptr, - struct wlr_buffer *button_released_ptr, - struct wlr_buffer *button_pressed_ptr, - struct wlr_buffer *button_blurred_ptr) -{ - BS_ASSERT(button_released_ptr->width == button_pressed_ptr->width); - BS_ASSERT(button_released_ptr->width == button_blurred_ptr->width); - BS_ASSERT(button_released_ptr->height == button_pressed_ptr->height); - BS_ASSERT(button_released_ptr->height == button_blurred_ptr->height); - - wlmaker_button_set_textures( - button_ptr->element.interactive_ptr, - button_released_ptr, - button_pressed_ptr, - button_blurred_ptr); - - if (NULL != button_ptr->element.margin_ptr) { - wlmaker_decorations_margin_set_size( - button_ptr->element.margin_ptr, - button_released_ptr->width, - button_released_ptr->height); - } -} - -/* ------------------------------------------------------------------------- */ -wlmaker_decorations_title_t *wlmaker_decorations_title_create( - struct wlr_scene_tree *wlr_scene_tree_ptr, - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr, - struct wlr_buffer *title_buffer_ptr, - struct wlr_buffer *title_blurred_buffer_ptr) -{ - BS_ASSERT(title_buffer_ptr->width == title_blurred_buffer_ptr->width); - BS_ASSERT(title_buffer_ptr->height == title_blurred_buffer_ptr->height); - - wlmaker_decorations_title_t *title_ptr = logged_calloc( - 1, sizeof(wlmaker_decorations_title_t)); - if (NULL == title_ptr) return NULL; - if (!wlmaker_decorations_element_init( - &title_ptr->element, - wlr_scene_tree_ptr, - view_ptr, - title_buffer_ptr->width, - title_buffer_ptr->height, - WLR_EDGE_LEFT | WLR_EDGE_TOP | WLR_EDGE_RIGHT)) { - wlmaker_decorations_title_destroy(title_ptr); - return NULL; - } - - title_ptr->element.interactive_ptr = wlmaker_titlebar_create( - title_ptr->element.wlr_scene_buffer_ptr, - cursor_ptr, - view_ptr, - title_buffer_ptr, - title_blurred_buffer_ptr); - if (NULL == title_ptr->element.interactive_ptr) { - wlmaker_decorations_title_destroy(title_ptr); - return NULL; - } - - wlmaker_interactive_focus( - title_ptr->element.interactive_ptr, - view_ptr->active); - - if (!bs_avltree_insert( - view_ptr->interactive_tree_ptr, - &title_ptr->element.interactive_ptr->wlr_scene_buffer_ptr->node, - &title_ptr->element.interactive_ptr->avlnode, - false)) { - bs_log(BS_ERROR, "Unexpected: Fail to insert into tree."); - wlmaker_decorations_title_destroy(title_ptr); - return NULL; - } - title_ptr->view_ptr = view_ptr; - - return title_ptr; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_title_destroy( - wlmaker_decorations_title_t *title_ptr) -{ - if (NULL != title_ptr->view_ptr) { - bs_avltree_delete( - title_ptr->view_ptr->interactive_tree_ptr, - &title_ptr->element.interactive_ptr->wlr_scene_buffer_ptr->node); - title_ptr->view_ptr = NULL; - } - - // The interactive is deleted in element_fini(). - - wlmaker_decorations_element_fini(&title_ptr->element); - free(title_ptr); -} - -/* ------------------------------------------------------------------------- */ -wlmaker_decorations_element_t *wlmaker_decorations_element_from_title( - wlmaker_decorations_title_t *title_ptr) -{ - return &title_ptr->element; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_title_set_textures( - wlmaker_decorations_title_t *title_ptr, - struct wlr_buffer *title_buffer_ptr, - struct wlr_buffer *title_blurred_buffer_ptr) -{ - BS_ASSERT(title_buffer_ptr->width == title_blurred_buffer_ptr->width); - BS_ASSERT(title_buffer_ptr->height == title_blurred_buffer_ptr->height); - - wlmaker_title_set_texture( - title_ptr->element.interactive_ptr, - title_buffer_ptr, - title_blurred_buffer_ptr); - - if (NULL != title_ptr->element.margin_ptr) { - wlmaker_decorations_margin_set_size( - title_ptr->element.margin_ptr, - title_buffer_ptr->width, - title_buffer_ptr->height); - } -} - -/* ------------------------------------------------------------------------- */ -wlmaker_decorations_resize_t *wlmaker_decorations_resize_create( - struct wlr_scene_tree *wlr_scene_tree_ptr, - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr, - struct wlr_buffer *resize_buffer_ptr, - struct wlr_buffer *resize_pressed_buffer_ptr, - uint32_t edges) -{ - BS_ASSERT(resize_buffer_ptr->width == resize_pressed_buffer_ptr->width); - BS_ASSERT(resize_buffer_ptr->height == resize_pressed_buffer_ptr->height); - - wlmaker_decorations_resize_t *resize_ptr = logged_calloc( - 1, sizeof(wlmaker_decorations_resize_t)); - if (NULL == resize_ptr) return NULL; - if (!wlmaker_decorations_element_init( - &resize_ptr->element, - wlr_scene_tree_ptr, - view_ptr, - resize_buffer_ptr->width, - resize_buffer_ptr->height, - edges)) { - wlmaker_decorations_resize_destroy(resize_ptr); - return NULL; - } - - resize_ptr->element.interactive_ptr = wlmaker_resizebar_create( - resize_ptr->element.wlr_scene_buffer_ptr, - cursor_ptr, - view_ptr, - resize_buffer_ptr, - resize_pressed_buffer_ptr, - edges); - if (NULL == resize_ptr->element.interactive_ptr) { - wlmaker_decorations_resize_destroy(resize_ptr); - return NULL; - } - - if (!bs_avltree_insert( - view_ptr->interactive_tree_ptr, - &resize_ptr->element.interactive_ptr->wlr_scene_buffer_ptr->node, - &resize_ptr->element.interactive_ptr->avlnode, - false)) { - bs_log(BS_ERROR, "Unexpected: Fail to insert into tree."); - wlmaker_decorations_resize_destroy(resize_ptr); - return NULL; - } - resize_ptr->view_ptr = view_ptr; - - return resize_ptr; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_resize_destroy( - wlmaker_decorations_resize_t *resize_ptr) -{ - if (NULL != resize_ptr->view_ptr) { - bs_avltree_delete( - resize_ptr->view_ptr->interactive_tree_ptr, - &resize_ptr->element.interactive_ptr->wlr_scene_buffer_ptr->node); - resize_ptr->view_ptr = NULL; - } - - // The interactive is deleted in element_fini(). - - wlmaker_decorations_element_fini(&resize_ptr->element); - free(resize_ptr); -} - -/* ------------------------------------------------------------------------- */ -wlmaker_decorations_element_t *wlmaker_decorations_element_from_resize( - wlmaker_decorations_resize_t *resize_ptr) -{ - return &resize_ptr->element; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_resize_set_textures( - wlmaker_decorations_resize_t *resize_ptr, - struct wlr_buffer *resize_buffer_ptr, - struct wlr_buffer *resize_pressed_buffer_ptr) -{ - BS_ASSERT(resize_buffer_ptr->width == resize_pressed_buffer_ptr->width); - BS_ASSERT(resize_buffer_ptr->height == resize_pressed_buffer_ptr->height); - - wlmaker_resizebar_set_textures( - resize_ptr->element.interactive_ptr, - resize_buffer_ptr, - resize_pressed_buffer_ptr); - - if (NULL != resize_ptr->element.margin_ptr) { - wlmaker_decorations_margin_set_size( - resize_ptr->element.margin_ptr, - resize_buffer_ptr->width, - resize_buffer_ptr->height); - } -} - -/* == End of element.c ===================================================== */ diff --git a/src/decorations/element.h b/src/decorations/element.h deleted file mode 100644 index d1fd2676..00000000 --- a/src/decorations/element.h +++ /dev/null @@ -1,219 +0,0 @@ -/* ========================================================================= */ -/** - * @file element.h - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef __ELEMENT_H__ -#define __ELEMENT_H__ - -#include "../view.h" - -#include "margin.h" - -#define WLR_USE_UNSTABLE -#include -#undef WLR_USE_UNSTABLE - -/** Forward declaration: Abstract base "class", the element. */ -typedef struct _wlmaker_decorations_element_t wlmaker_decorations_element_t; -/** Forward declaration: Button. */ -typedef struct _wlmaker_decorations_button_t wlmaker_decorations_button_t; -/** Forward declaration: Title. */ -typedef struct _wlmaker_decorations_title_t wlmaker_decorations_title_t; -/** Forward declaration: An element of the resize bar. */ -typedef struct _wlmaker_decorations_resize_t wlmaker_decorations_resize_t; - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * Initializes the element. - * - * An element is a wrapper around an "interactive", and adds the scene graph - * node and optionally margins. On the long run, this should be unified with - * the interactive. - * - * @param element_ptr - * @param wlr_scene_tree_ptr The container's scene graph tree. - * @param data_ptr Data to set in the scene node's `data` field. - * @param width Of the element, used for adding margins. - * @param height Of the element, used for adding margins. - * @param edges Which edges to add as margins, or 0 for none. - */ -bool wlmaker_decorations_element_init( - wlmaker_decorations_element_t *element_ptr, - struct wlr_scene_tree *wlr_scene_tree_ptr, - void *data_ptr, - unsigned width, - unsigned height, - uint32_t edges); - -/** - * Releases all resources for the element. - * - * @param element_ptr - */ -void wlmaker_decorations_element_fini( - wlmaker_decorations_element_t *element_ptr); - -/** - * Sets position of the element, relative to the parent's scene graph tree. - * - * @param element_ptr - * @param x - * @param y - */ -void wlmaker_decorations_element_set_position( - wlmaker_decorations_element_t *element_ptr, - int x, - int y); - -/** - * Creates a button element, wrapping an "element" on the button interactive. - * - * @param wlr_scene_tree_ptr - * @param cursor_ptr - * @param callback - * @param view_ptr - * @param button_released_ptr - * @param button_pressed_ptr - * @param button_blurred_ptr - * @param edges - * - * @return A pointer to the button, or NULL on error. Must be free-d via - * @ref wlmaker_decorations_button_destroy. - */ -wlmaker_decorations_button_t *wlmaker_decorations_button_create( - struct wlr_scene_tree *wlr_scene_tree_ptr, - wlmaker_cursor_t *cursor_ptr, - wlmaker_interactive_callback_t callback, - wlmaker_view_t *view_ptr, - struct wlr_buffer *button_released_ptr, - struct wlr_buffer *button_pressed_ptr, - struct wlr_buffer *button_blurred_ptr, - uint32_t edges); - -/** Destroys the button element. */ -void wlmaker_decorations_button_destroy( - wlmaker_decorations_button_t *button_ptr); - -/** Returns the base "element" for the button. */ -wlmaker_decorations_element_t *wlmaker_decorations_element_from_button( - wlmaker_decorations_button_t *button_ptr); - -/** - * Updates the textures used for the button. - * - * @param button_ptr - * @param button_released_ptr - * @param button_pressed_ptr - * @param button_blurred_ptr - */ -void wlmaker_decorations_button_set_textures( - wlmaker_decorations_button_t *button_ptr, - struct wlr_buffer *button_released_ptr, - struct wlr_buffer *button_pressed_ptr, - struct wlr_buffer *button_blurred_ptr); - -/** - * Creates a title element, wrapping an "element" on the titlebar interactive. - * - * @param wlr_scene_tree_ptr - * @param cursor_ptr - * @param view_ptr - * @param title_buffer_ptr - * @param title_blurred_buffer_ptr - * - * @return A pointer to the title, must be free-d via - * @ref wlmaker_decorations_title_destroy. - */ -wlmaker_decorations_title_t *wlmaker_decorations_title_create( - struct wlr_scene_tree *wlr_scene_tree_ptr, - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr, - struct wlr_buffer *title_buffer_ptr, - struct wlr_buffer *title_blurred_buffer_ptr); - -/** Destroys the title element. */ -void wlmaker_decorations_title_destroy( - wlmaker_decorations_title_t *title_ptr); - -/** Returns the base "element" for the title. */ -wlmaker_decorations_element_t *wlmaker_decorations_element_from_title( - wlmaker_decorations_title_t *title_ptr); - -/** - * Updates the textures used for the title. - * - * @param title_ptr - * @param title_buffer_ptr - * @param title_blurred_buffer_ptr - */ -void wlmaker_decorations_title_set_textures( - wlmaker_decorations_title_t *title_ptr, - struct wlr_buffer *title_buffer_ptr, - struct wlr_buffer *title_blurred_buffer_ptr); - -/** - * Creates a resizebar, wrapping an "element" on the resizebar interactive. - * - * @param wlr_scene_tree_ptr - * @param cursor_ptr - * @param view_ptr - * @param resize_buffer_ptr - * @param resize_pressed_buffer_ptr - * @param edges Edges controlled by this resizebar. Also used - * to deduce edges for the margins. - * - * @return A pointer to the resizebar, must be free-d via - * @ref wlmaker_decorations_resize_destroy. - */ -wlmaker_decorations_resize_t *wlmaker_decorations_resize_create( - struct wlr_scene_tree *wlr_scene_tree_ptr, - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr, - struct wlr_buffer *resize_buffer_ptr, - struct wlr_buffer *resize_pressed_buffer_ptr, - uint32_t edges); - -/** Destroys the resize element. */ -void wlmaker_decorations_resize_destroy( - wlmaker_decorations_resize_t *resize_ptr); - -/** Returns the base "element" for the resize. */ -wlmaker_decorations_element_t *wlmaker_decorations_element_from_resize( - wlmaker_decorations_resize_t *resize_ptr); - -/** - * Updates the textures used for the resize. - * - * @param resize_ptr - * @param resize_buffer_ptr - * @param resize_pressed_buffer_ptr - */ -void wlmaker_decorations_resize_set_textures( - wlmaker_decorations_resize_t *resize_ptr, - struct wlr_buffer *resize_buffer_ptr, - struct wlr_buffer *resize_pressed_buffer_ptr); - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif /* __ELEMENT_H__ */ -/* == End of element.h ===================================================== */ diff --git a/src/decorations/margin.c b/src/decorations/margin.c deleted file mode 100644 index 593a492f..00000000 --- a/src/decorations/margin.c +++ /dev/null @@ -1,295 +0,0 @@ -/* ========================================================================= */ -/** - * @file margin.c - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "margin.h" - -#include "../config.h" - -#include - -/* == Declarations ========================================================= */ - -/** Handle for margin. */ -struct _wlmaker_decorations_margin_t { - /** Parent's WLR scene tree. */ - struct wlr_scene_tree *parent_wlr_scene_tree_ptr; - - /** Width of the element surrounded by the margin(s). */ - unsigned width; - /** Height of the surrounded element. */ - unsigned height; - /** X-coordinate of the top-left corner of the decorated area. */ - int x; - /** Y-coordinate of the top-left corner of the decorated area. */ - int y; - /** Which edges of the margin should be drawn. */ - uint32_t edges; - - /** Scene rectangle holding the left edge of the margin. If any. */ - struct wlr_scene_rect *left_rect_ptr; - /** Scene rectangle holding the top edge of the margin. If any. */ - struct wlr_scene_rect *top_rect_ptr; - /** Scene rectangle holding the right edge of the margin. If any. */ - struct wlr_scene_rect *right_rect_ptr; - /** Scene rectangle holding the bottom edge of the margin. If any. */ - struct wlr_scene_rect *bottom_rect_ptr; -}; - -static bool recreate_edges( - wlmaker_decorations_margin_t *margin_ptr, - uint32_t edges); -static struct wlr_scene_rect *create_rect( - struct wlr_scene_tree *decoration_wlr_scene_tree_ptr, - uint32_t color); -static void rect_set_size( - struct wlr_scene_rect *wlr_scene_rect_ptr, - unsigned width, - unsigned height); -static void rect_set_position( - struct wlr_scene_rect *wlr_scene_rect_ptr, - int x, - int y); - -/* == Exported methods ===================================================== */ - -/* ------------------------------------------------------------------------- */ -wlmaker_decorations_margin_t *wlmaker_decorations_margin_create( - struct wlr_scene_tree *wlr_scene_tree_ptr, - int x, int y, - unsigned width, unsigned height, - uint32_t edges) -{ - wlmaker_decorations_margin_t *margin_ptr = logged_calloc( - 1, sizeof(wlmaker_decorations_margin_t)); - if (NULL == margin_ptr) return NULL; - margin_ptr->parent_wlr_scene_tree_ptr = wlr_scene_tree_ptr; - - if (!recreate_edges(margin_ptr, edges)) { - wlmaker_decorations_margin_destroy(margin_ptr); - return NULL; - } - wlmaker_decorations_margin_set_position(margin_ptr, x, y); - wlmaker_decorations_margin_set_size(margin_ptr, width, height); - return margin_ptr; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_margin_destroy( - wlmaker_decorations_margin_t *margin_ptr) -{ - if (NULL != margin_ptr->bottom_rect_ptr) { - wlr_scene_node_destroy(&margin_ptr->bottom_rect_ptr->node); - margin_ptr->bottom_rect_ptr = NULL; - } - if (NULL != margin_ptr->right_rect_ptr) { - wlr_scene_node_destroy(&margin_ptr->right_rect_ptr->node); - margin_ptr->right_rect_ptr = NULL; - } - if (NULL != margin_ptr->top_rect_ptr) { - wlr_scene_node_destroy(&margin_ptr->top_rect_ptr->node); - margin_ptr->top_rect_ptr = NULL; - } - if (NULL != margin_ptr->left_rect_ptr) { - wlr_scene_node_destroy(&margin_ptr->left_rect_ptr->node); - margin_ptr->left_rect_ptr = NULL; - } - - free(margin_ptr); -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_margin_set_position( - wlmaker_decorations_margin_t *margin_ptr, - int x, - int y) -{ - unsigned margin_width = wlmaker_config_theme.window_margin_width; - - int hx = 0; - if (margin_ptr->edges & WLR_EDGE_LEFT) { - hx -= margin_width; - } - - rect_set_position(margin_ptr->left_rect_ptr, - x - margin_width, y); - rect_set_position(margin_ptr->top_rect_ptr, - x + hx, y - margin_width); - rect_set_position(margin_ptr->right_rect_ptr, - x + margin_ptr->width, y); - rect_set_position(margin_ptr->bottom_rect_ptr, - x + hx, y + margin_ptr->height); - - margin_ptr->x = x; - margin_ptr->y = y; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_margin_set_size( - wlmaker_decorations_margin_t *margin_ptr, - unsigned width, - unsigned height) -{ - unsigned margin_width = wlmaker_config_theme.window_margin_width; - - // Horizontal margin area will cover the "corner" area if both margins - // are set. - unsigned hwidth = width; - if (margin_ptr->edges & WLR_EDGE_LEFT) { - hwidth += wlmaker_config_theme.window_margin_width; - } - if (margin_ptr->edges & WLR_EDGE_RIGHT) { - hwidth += wlmaker_config_theme.window_margin_width; - } - rect_set_size(margin_ptr->left_rect_ptr, margin_width, height); - rect_set_size(margin_ptr->top_rect_ptr, hwidth, margin_width); - rect_set_size(margin_ptr->right_rect_ptr, margin_width, height); - rect_set_size(margin_ptr->bottom_rect_ptr, hwidth, margin_width); - - margin_ptr->width = width; - margin_ptr->height = height; - - // Need to update the position of the margins. - wlmaker_decorations_margin_set_position( - margin_ptr, margin_ptr->x, margin_ptr->y); - -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_margin_set_edges( - wlmaker_decorations_margin_t *margin_ptr, - uint32_t edges) -{ - BS_ASSERT(recreate_edges(margin_ptr, edges)); - wlmaker_decorations_margin_set_position( - margin_ptr, margin_ptr->x, margin_ptr->y); - wlmaker_decorations_margin_set_size( - margin_ptr, margin_ptr->width, margin_ptr->height); -} - -/* == Local (static) methods =============================================== */ - -/* ------------------------------------------------------------------------- */ -/** Re-creates the rectangles for the specified edges. */ -bool recreate_edges( - wlmaker_decorations_margin_t *margin_ptr, - uint32_t edges) -{ - uint32_t col = wlmaker_config_theme.window_margin_color; - struct wlr_scene_tree *wlr_scene_tree_ptr = - margin_ptr->parent_wlr_scene_tree_ptr; - - if (edges & WLR_EDGE_LEFT) { - margin_ptr->left_rect_ptr = create_rect(wlr_scene_tree_ptr, col); - if (NULL == margin_ptr->left_rect_ptr) return false; - } else if (NULL != margin_ptr->left_rect_ptr) { - wlr_scene_node_destroy(&margin_ptr->left_rect_ptr->node); - margin_ptr->left_rect_ptr = NULL; - } - - if (edges & WLR_EDGE_TOP) { - margin_ptr->top_rect_ptr = create_rect(wlr_scene_tree_ptr, col); - if (NULL == margin_ptr->top_rect_ptr) return false; - } else if (NULL != margin_ptr->top_rect_ptr) { - wlr_scene_node_destroy(&margin_ptr->top_rect_ptr->node); - margin_ptr->top_rect_ptr = NULL; - } - - if (edges & WLR_EDGE_RIGHT) { - margin_ptr->right_rect_ptr = create_rect(wlr_scene_tree_ptr, col); - if (NULL == margin_ptr->right_rect_ptr) return false; - } else if (NULL != margin_ptr->right_rect_ptr) { - wlr_scene_node_destroy(&margin_ptr->right_rect_ptr->node); - margin_ptr->right_rect_ptr = NULL; - } - - if (edges & WLR_EDGE_BOTTOM) { - margin_ptr->bottom_rect_ptr = create_rect(wlr_scene_tree_ptr, col); - if (NULL == margin_ptr->bottom_rect_ptr) return false; - } else if (NULL != margin_ptr->bottom_rect_ptr) { - wlr_scene_node_destroy(&margin_ptr->bottom_rect_ptr->node); - margin_ptr->bottom_rect_ptr = NULL; - } - margin_ptr->edges = edges; - return true; -} -/* ------------------------------------------------------------------------- */ -/** - * Helper: Creates a `wlr_scene_rect` with the given `color`. - * - * The rectangle will not be set to correct size nor position. Use - * @ref rect_set_size and @ref rect_set_position for that. - * - * @param decoration_wlr_scene_tree_ptr - * @param color As an ARGB8888 32-bit value. - */ -struct wlr_scene_rect *create_rect( - struct wlr_scene_tree *decoration_wlr_scene_tree_ptr, uint32_t color) -{ - float fcolor[4]; - bs_gfxbuf_argb8888_to_floats( - color, &fcolor[0], &fcolor[1], &fcolor[2], &fcolor[3]); - struct wlr_scene_rect *wlr_scene_rect_ptr = wlr_scene_rect_create( - decoration_wlr_scene_tree_ptr, 1, 1, fcolor); - if (NULL == wlr_scene_rect_ptr) { - bs_log(BS_ERROR, "Failed wlr_scene_rect_create(%p, 1, 1, %"PRIx32")", - decoration_wlr_scene_tree_ptr, color); - return NULL; - } - wlr_scene_node_set_enabled(&wlr_scene_rect_ptr->node, true); - return wlr_scene_rect_ptr; -} - -/* ------------------------------------------------------------------------- */ -/** - * Helper: Updates dimensions of the `wlr_scene_rect`. - * - * @param wlr_scene_rect_ptr The rectangle to update. May be NULL. - * @param width - * @param height - */ -static void rect_set_size( - struct wlr_scene_rect *wlr_scene_rect_ptr, - unsigned width, - unsigned height) -{ - if (NULL == wlr_scene_rect_ptr) return; - wlr_scene_rect_set_size( - wlr_scene_rect_ptr, width, height); -} - -/* ------------------------------------------------------------------------- */ -/** - * Helper: Updates position of the `wlr_scene_rect`. - * - * @param wlr_scene_rect_ptr The rectangle to update. May be NULL. - * @param x Position relative to the decorated window. - * @param y - */ -static void rect_set_position( - struct wlr_scene_rect *wlr_scene_rect_ptr, - int x, - int y) -{ - if (NULL == wlr_scene_rect_ptr) return; - wlr_scene_node_set_position( - &wlr_scene_rect_ptr->node, x, y); -} - -/* == End of margin.c ====================================================== */ diff --git a/src/decorations/margin.h b/src/decorations/margin.h deleted file mode 100644 index 8cc64564..00000000 --- a/src/decorations/margin.h +++ /dev/null @@ -1,94 +0,0 @@ -/* ========================================================================= */ -/** - * @file margin.h - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef __MARGIN_H__ -#define __MARGIN_H__ - -#include - -#include - -#define WLR_USE_UNSTABLE -#include -#undef WLR_USE_UNSTABLE - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** Forward definition: Handle for margin. */ -typedef struct _wlmaker_decorations_margin_t wlmaker_decorations_margin_t; - -/** Creates margin. */ -wlmaker_decorations_margin_t *wlmaker_decorations_margin_create( - struct wlr_scene_tree *wlr_scene_tree_ptr, - int x, int y, - unsigned width, unsigned height, - uint32_t edges); - -/** Destroys margin. */ -void wlmaker_decorations_margin_destroy( - wlmaker_decorations_margin_t *margin_ptr); - -/** - * Sets the position of the margins. - * - * The given (x, y) coordinates are defining the top-left corner of the - * decorated area, not including the margin itself. - * - * @param margin_ptr - * @param x - * @param y - */ -void wlmaker_decorations_margin_set_position( - wlmaker_decorations_margin_t *margin_ptr, - int x, - int y); - -/** - * Resizes the margins. - * - * `width` and `height` are describing the dimensions of the decorated element, - * excluding the added dimensions of the margin. - * - * @param margin_ptr - * @param width - * @param height - */ -void wlmaker_decorations_margin_set_size( - wlmaker_decorations_margin_t *margin_ptr, - unsigned width, - unsigned height); - -/** - * (re)configures which edges to show for the margin. - * - * @param margin_ptr - * @param edges - */ -void wlmaker_decorations_margin_set_edges( - wlmaker_decorations_margin_t *margin_ptr, - uint32_t edges); - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif /* __MARGIN_H__ */ -/* == End of margin.h ====================================================== */ diff --git a/src/decorations/resizebar.c b/src/decorations/resizebar.c deleted file mode 100644 index 0a630763..00000000 --- a/src/decorations/resizebar.c +++ /dev/null @@ -1,312 +0,0 @@ -/* ========================================================================= */ -/** - * @file resizebar.c - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "resizebar.h" - -#include "element.h" -#include "../config.h" -#include "../resizebar.h" - -/* == Declarations ========================================================= */ - -/** State of a window's resize bar. */ -struct _wlmaker_decorations_resizebar_t { - /** Back-link to the view it decorates. */ - wlmaker_view_t *view_ptr; - - /** Scene tree, for just the resize bar elements and margin. */ - struct wlr_scene_tree *wlr_scene_tree_ptr; - - /** Left element of the resize bar, or NULL if not set. */ - wlmaker_decorations_resize_t *left_resize_ptr; - /** Center element of the resize bar, or NULL if not set. */ - wlmaker_decorations_resize_t *center_resize_ptr; - /** Right element of the resize bar. */ - wlmaker_decorations_resize_t *right_resize_ptr; - - /** Width of the left element, or 0 if not set. */ - unsigned left_width; - /** Width of the center element, or 0 if not set. */ - unsigned center_width; - /** Width of the right element. */ - unsigned right_width; - - /** Overall width of the decorated window. */ - unsigned width; - /** Height of the decorated window. */ - unsigned height; -}; - -/** Hardcoded: Width of bezel. */ -static const uint32_t bezel_width = 1; -/** Hardcoded: Height of the resize bar. */ -static const uint32_t resizebar_height = 7; -/** Hardcoded: Width of the corner elements of the resize bar. */ -static const uint32_t resizebar_corner_width = 29; - -static void set_width( - wlmaker_decorations_resizebar_t *resizebar_ptr, - unsigned width); -static bs_gfxbuf_t *create_background(unsigned width); -static void create_or_update_resize( - wlmaker_decorations_resizebar_t *resizebar_ptr, - wlmaker_decorations_resize_t **resize_ptr_ptr, - bs_gfxbuf_t *background_gfxbuf_ptr, - int pos, unsigned width, - uint32_t edges); - -/* == Exported methods ===================================================== */ - -/* ------------------------------------------------------------------------- */ -wlmaker_decorations_resizebar_t *wlmaker_decorations_resizebar_create( - struct wlr_scene_tree *wlr_scene_tree_ptr, - unsigned width, - unsigned height, - wlmaker_view_t *view_ptr) -{ - wlmaker_decorations_resizebar_t *resizebar_ptr = logged_calloc( - 1, sizeof(wlmaker_decorations_resizebar_t)); - if (NULL == resizebar_ptr) return NULL; - resizebar_ptr->view_ptr = view_ptr; - - resizebar_ptr->wlr_scene_tree_ptr = wlr_scene_tree_create( - wlr_scene_tree_ptr); - if (NULL == resizebar_ptr->wlr_scene_tree_ptr) { - wlmaker_decorations_resizebar_destroy(resizebar_ptr); - return NULL; - } - wlr_scene_node_set_position( - &resizebar_ptr->wlr_scene_tree_ptr->node, - 0, height + wlmaker_config_theme.window_margin_width); - - wlmaker_decorations_resizebar_set_size(resizebar_ptr, width, height); - return resizebar_ptr; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_resizebar_destroy( - wlmaker_decorations_resizebar_t *resizebar_ptr) -{ - if (NULL != resizebar_ptr->right_resize_ptr) { - wlmaker_decorations_resize_destroy(resizebar_ptr->right_resize_ptr); - resizebar_ptr->right_resize_ptr = NULL; - } - if (NULL != resizebar_ptr->center_resize_ptr) { - wlmaker_decorations_resize_destroy(resizebar_ptr->center_resize_ptr); - resizebar_ptr->center_resize_ptr = NULL; - } - if (NULL != resizebar_ptr->left_resize_ptr) { - wlmaker_decorations_resize_destroy(resizebar_ptr->left_resize_ptr); - resizebar_ptr->left_resize_ptr = NULL; - } - - if (NULL != resizebar_ptr->wlr_scene_tree_ptr) { - wlr_scene_node_destroy(&resizebar_ptr->wlr_scene_tree_ptr->node); - resizebar_ptr->wlr_scene_tree_ptr = NULL; - } - - free(resizebar_ptr); -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_resizebar_set_size( - wlmaker_decorations_resizebar_t *resizebar_ptr, - unsigned width, - unsigned height) -{ - if (width == resizebar_ptr->width && - height == resizebar_ptr->height) return; - set_width(resizebar_ptr, width); - - wlr_scene_node_set_position( - &resizebar_ptr->wlr_scene_tree_ptr->node, - 0, height + wlmaker_config_theme.window_margin_width); - - unsigned bar_y = 0; - - if (0 < resizebar_ptr->left_width) { - wlmaker_decorations_element_set_position( - wlmaker_decorations_element_from_resize( - resizebar_ptr->left_resize_ptr), - 0, bar_y); - } - - if (0 < resizebar_ptr->center_width) { - wlmaker_decorations_element_set_position( - wlmaker_decorations_element_from_resize( - resizebar_ptr->center_resize_ptr), - resizebar_ptr->left_width, bar_y); - } - - wlmaker_decorations_element_set_position( - wlmaker_decorations_element_from_resize( - resizebar_ptr->right_resize_ptr), - width - resizebar_ptr->right_width, bar_y); - - resizebar_ptr->height = height; - return; -} - -/* ------------------------------------------------------------------------- */ -unsigned wlmaker_decorations_resizebar_get_height( - __UNUSED__ wlmaker_decorations_resizebar_t *resizebar_ptr) -{ - return resizebar_height + wlmaker_config_theme.window_margin_width; -} - -/* == Local (static) methods =============================================== */ - -/* ------------------------------------------------------------------------- */ -/** Applies the width to the resizebar, re-creating elements if needed. */ -static void set_width( - wlmaker_decorations_resizebar_t *resizebar_ptr, - unsigned width) -{ - if (width == resizebar_ptr->width) return; - - resizebar_ptr->left_width = resizebar_corner_width; - resizebar_ptr->right_width = resizebar_corner_width; - if (width > 2 * resizebar_corner_width) { - resizebar_ptr->center_width = width - 2 * resizebar_corner_width; - } else if (width > resizebar_corner_width) { - resizebar_ptr->center_width = 0; - resizebar_ptr->left_width = width - resizebar_corner_width; - } else { - resizebar_ptr->left_width = 0; - resizebar_ptr->right_width = width; - } - - BS_ASSERT(resizebar_ptr->left_width + - resizebar_ptr->right_width + - resizebar_ptr->center_width == width); - - bs_gfxbuf_t *gfxbuf_ptr = create_background(width); - BS_ASSERT(NULL != gfxbuf_ptr); - - if (0 < resizebar_ptr->left_width) { - create_or_update_resize( - resizebar_ptr, - &resizebar_ptr->left_resize_ptr, - gfxbuf_ptr, - 0, resizebar_ptr->left_width, - WLR_EDGE_LEFT | WLR_EDGE_BOTTOM); - } else if (NULL != resizebar_ptr->left_resize_ptr) { - wlmaker_decorations_resize_destroy(resizebar_ptr->left_resize_ptr); - resizebar_ptr->left_resize_ptr = NULL; - } - - if (0 < resizebar_ptr->center_width) { - create_or_update_resize( - resizebar_ptr, - &resizebar_ptr->center_resize_ptr, - gfxbuf_ptr, - resizebar_ptr->left_width, resizebar_ptr->center_width, - WLR_EDGE_BOTTOM); - } else if (NULL != resizebar_ptr->center_resize_ptr) { - wlmaker_decorations_resize_destroy(resizebar_ptr->center_resize_ptr); - resizebar_ptr->center_resize_ptr = NULL; - } - - create_or_update_resize( - resizebar_ptr, - &resizebar_ptr->right_resize_ptr, - gfxbuf_ptr, - width - resizebar_ptr->right_width, resizebar_ptr->right_width, - WLR_EDGE_RIGHT | WLR_EDGE_BOTTOM); - - bs_gfxbuf_destroy(gfxbuf_ptr); - resizebar_ptr->width = width; -} - -/* ------------------------------------------------------------------------- */ -/** Creates the background texture at givenm width. */ -bs_gfxbuf_t *create_background(unsigned width) -{ - bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(width, resizebar_height); - if (NULL == gfxbuf_ptr) return NULL; - - cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); - if (NULL == cairo_ptr) { - bs_gfxbuf_destroy(gfxbuf_ptr); - return false; - } - wlmaker_primitives_cairo_fill( - cairo_ptr, &wlmaker_config_theme.resizebar_fill); - cairo_destroy(cairo_ptr); - - return gfxbuf_ptr; -} - -/* ------------------------------------------------------------------------- */ -/** Creates or updates a resizebar element. */ -void create_or_update_resize( - wlmaker_decorations_resizebar_t *resizebar_ptr, - wlmaker_decorations_resize_t **resize_ptr_ptr, - bs_gfxbuf_t *background_gfxbuf_ptr, - int pos, unsigned width, - uint32_t edges) -{ - cairo_t *cairo_ptr; - - struct wlr_buffer *released_wlrbuf_ptr = bs_gfxbuf_create_wlr_buffer( - width, resizebar_height); - BS_ASSERT(NULL != released_wlrbuf_ptr); - bs_gfxbuf_copy_area( - bs_gfxbuf_from_wlr_buffer(released_wlrbuf_ptr), 0, 0, - background_gfxbuf_ptr, pos, 0, - width, resizebar_height); - cairo_ptr = cairo_create_from_wlr_buffer(released_wlrbuf_ptr); - BS_ASSERT(NULL != cairo_ptr); - wlmaker_primitives_draw_bezel(cairo_ptr, bezel_width, true); - cairo_destroy(cairo_ptr); - - struct wlr_buffer *pressed_wlrbuf_ptr = bs_gfxbuf_create_wlr_buffer( - width, resizebar_height); - BS_ASSERT(NULL != released_wlrbuf_ptr); - bs_gfxbuf_copy_area( - bs_gfxbuf_from_wlr_buffer(pressed_wlrbuf_ptr), 0, 0, - background_gfxbuf_ptr, pos, 0, - width, resizebar_height); - cairo_ptr = cairo_create_from_wlr_buffer(pressed_wlrbuf_ptr); - BS_ASSERT(NULL != cairo_ptr); - wlmaker_primitives_draw_bezel(cairo_ptr, bezel_width, false); - cairo_destroy(cairo_ptr); - - if (NULL == *resize_ptr_ptr) { - *resize_ptr_ptr = wlmaker_decorations_resize_create( - resizebar_ptr->wlr_scene_tree_ptr, - resizebar_ptr->view_ptr->server_ptr->cursor_ptr, - resizebar_ptr->view_ptr, - released_wlrbuf_ptr, - pressed_wlrbuf_ptr, - edges); - BS_ASSERT(NULL != *resize_ptr_ptr); - } else { - wlmaker_decorations_resize_set_textures( - *resize_ptr_ptr, - released_wlrbuf_ptr, - pressed_wlrbuf_ptr); - } - - wlr_buffer_drop(pressed_wlrbuf_ptr); - wlr_buffer_drop(released_wlrbuf_ptr); -} - -/* == End of resizebar.c =================================================== */ diff --git a/src/decorations/resizebar.h b/src/decorations/resizebar.h deleted file mode 100644 index 47174dd2..00000000 --- a/src/decorations/resizebar.h +++ /dev/null @@ -1,86 +0,0 @@ -/* ========================================================================= */ -/** - * @file resizebar.h - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef __RESIZEBAR_H__ -#define __RESIZEBAR_H__ - -#define WLR_USE_UNSTABLE -#include -#undef WLR_USE_UNSTABLE - -#include "../view.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** Forward declaration: Resizebar of window decoration. */ -typedef struct _wlmaker_decorations_resizebar_t wlmaker_decorations_resizebar_t; - -/** - * Creates the title bar for window decoration. - * - * @param wlr_scene_tree_ptr - * @param width - * @param height - * @param view_ptr - * - * @return A resizebar handle, or NULL on error. Must be free-d by calling - * @ref wlmaker_decorations_resizebar_destroy. - */ -wlmaker_decorations_resizebar_t *wlmaker_decorations_resizebar_create( - struct wlr_scene_tree *wlr_scene_tree_ptr, - unsigned width, - unsigned height, - wlmaker_view_t *view_ptr); - -/** - * Destroys a window decoration resize bar. - * - * @param resizebar_ptr - */ -void wlmaker_decorations_resizebar_destroy( - wlmaker_decorations_resizebar_t *resizebar_ptr); - -/** - * Sets the width of the resizebar. - * - * @param resizebar_ptr - * @param width - * @param height - */ -void wlmaker_decorations_resizebar_set_size( - wlmaker_decorations_resizebar_t *resizebar_ptr, - unsigned width, - unsigned height); - -/** - * Returns the height of the resizebar. Includes the bottom margin. - * - * @param resizebar_ptr - */ -unsigned wlmaker_decorations_resizebar_get_height( - wlmaker_decorations_resizebar_t *resizebar_ptr); - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif /* __RESIZEBAR_H__ */ -/* == End of resizebar.h =================================================== */ diff --git a/src/decorations/titlebar.c b/src/decorations/titlebar.c deleted file mode 100644 index e0ae949e..00000000 --- a/src/decorations/titlebar.c +++ /dev/null @@ -1,634 +0,0 @@ -/* ========================================================================= */ -/** - * @file titlebar.c - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "titlebar.h" - -#include - -#include "element.h" -#include "../config.h" -#include "toolkit/toolkit.h" - -/* == Declarations ========================================================= */ - -/** State of a window's titlebar, including buttons and title area. */ -struct _wlmaker_decorations_titlebar_t { - /** Back-link to the view it decorates. */ - wlmaker_view_t *view_ptr; - - /** Scene tree, for just the title bar elements and margin. */ - struct wlr_scene_tree *wlr_scene_tree_ptr; - - /** "Minimize" button element. */ - wlmaker_decorations_button_t *minimize_button_ptr; - /** "Close" button element. */ - wlmaker_decorations_button_t *close_button_ptr; - /** "Title" element. */ - wlmaker_decorations_title_t *title_ptr; - - /** Background graphics buffer, focussed window. */ - bs_gfxbuf_t *background_focussed_gfxbuf_ptr; - /** Background graphics buffer, blurred window. */ - bs_gfxbuf_t *background_blurred_gfxbuf_ptr; - - /** Currently configured width, excluding the outer margins. */ - unsigned width; - /** Position of the title element, relative to the scene tree. */ - int title_pos; - /** Width of the title element. */ - unsigned title_width; - /** Position of the "close" button, relative to the scene tree. */ - int close_pos; -}; - -/** Holder for a few `struct wlr_buffer` textures, for buttons & title. */ -typedef struct { - /** Texture in released state. */ - struct wlr_buffer *released_wlrbuf_ptr; - /** Texture in pressed state, or NULL. */ - struct wlr_buffer *pressed_wlrbuf_ptr; - /** Texture in blurred state. */ - struct wlr_buffer *blurred_wlrbuf_ptr; -} wlr_buffer_holder_t; - -static bool recreate_backgrounds( - wlmaker_decorations_titlebar_t *titlebar_ptr, - unsigned width); - -static bool create_wlr_buffers( - wlr_buffer_holder_t *buffer_holder_ptr, - unsigned width, - bool press); -static void drop_wlr_buffers(wlr_buffer_holder_t *buffer_holder_ptr); - -static void create_or_update_minimize_button( - wlmaker_decorations_titlebar_t *titlebar_ptr); -static void create_or_update_close_button( - wlmaker_decorations_titlebar_t *titlebar_ptr); -static void create_or_update_title( - wlmaker_decorations_titlebar_t *titlebar_ptr); - -static void button_minimize_callback( - wlmaker_interactive_t *interactive_ptr, - void *data_ptr); -static void button_close_callback( - wlmaker_interactive_t *interactive_ptr, - void *data_ptr); - -/** Hardcoded: Width of the window buttons. */ -static const unsigned wlmaker_decorations_button_width = 22; -/** Hardcoded: Height of the title bar, in pixels. */ -static const unsigned wlmaker_decorations_titlebar_height = 22; - -/** Hardcoded: Width of the bezel for buttons. */ -static const unsigned wlmaker_decorations_button_bezel_width = 1; - -/** - * Attempted minimal width of the title. If the title width falls below that - * value, buttons will be dropped instead. - */ -static const unsigned title_min_width = wlmaker_decorations_button_width; - -/* == Exported methods ===================================================== */ - - -/* ------------------------------------------------------------------------- */ -wlmaker_decorations_titlebar_t *wlmaker_decorations_titlebar_create( - struct wlr_scene_tree *wlr_scene_tree_ptr, - unsigned width, - wlmaker_view_t *view_ptr) -{ - wlmaker_decorations_titlebar_t *titlebar_ptr = logged_calloc( - 1, sizeof(wlmaker_decorations_titlebar_t)); - if (NULL == titlebar_ptr) return NULL; - titlebar_ptr->view_ptr = view_ptr; - - titlebar_ptr->wlr_scene_tree_ptr = wlr_scene_tree_create( - wlr_scene_tree_ptr); - if (NULL == titlebar_ptr->wlr_scene_tree_ptr) { - wlmaker_decorations_titlebar_destroy(titlebar_ptr); - return NULL; - } - wlr_scene_node_set_position( - &titlebar_ptr->wlr_scene_tree_ptr->node, - 0, - wlmaker_decorations_titlebar_height - - wlmaker_config_theme.window_margin_width); - - wlmaker_decorations_titlebar_set_width(titlebar_ptr, width); - return titlebar_ptr; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_titlebar_destroy( - wlmaker_decorations_titlebar_t *titlebar_ptr) -{ - if (NULL != titlebar_ptr->title_ptr) { - wlmaker_decorations_title_destroy(titlebar_ptr->title_ptr); - titlebar_ptr->title_ptr = NULL; - } - - if (NULL != titlebar_ptr->close_button_ptr) { - wlmaker_decorations_button_destroy(titlebar_ptr->close_button_ptr); - titlebar_ptr->close_button_ptr = NULL; - } - - if (NULL != titlebar_ptr->minimize_button_ptr) { - wlmaker_decorations_button_destroy(titlebar_ptr->minimize_button_ptr); - titlebar_ptr->minimize_button_ptr = NULL; - } - - if (NULL != titlebar_ptr->background_focussed_gfxbuf_ptr) { - bs_gfxbuf_destroy(titlebar_ptr->background_focussed_gfxbuf_ptr); - titlebar_ptr->background_focussed_gfxbuf_ptr = NULL; - } - if (NULL != titlebar_ptr->background_blurred_gfxbuf_ptr) { - bs_gfxbuf_destroy(titlebar_ptr->background_blurred_gfxbuf_ptr); - titlebar_ptr->background_blurred_gfxbuf_ptr = NULL; - } - - if (NULL != titlebar_ptr->wlr_scene_tree_ptr) { - wlr_scene_node_destroy(&titlebar_ptr->wlr_scene_tree_ptr->node); - titlebar_ptr->wlr_scene_tree_ptr = NULL; - } - - free(titlebar_ptr); -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_titlebar_set_width( - wlmaker_decorations_titlebar_t *titlebar_ptr, - unsigned width) -{ - bool has_close, has_minimize; - - if (width == titlebar_ptr->width) return; - - // The 'minimize' button is shown only if there's space for everything. - if (width > title_min_width + - 2 * wlmaker_decorations_button_width + - 2 * wlmaker_config_theme.window_margin_width) { - titlebar_ptr->title_pos = - wlmaker_decorations_button_width + - wlmaker_config_theme.window_margin_width; - has_minimize = true; - } else { - has_minimize = false; - titlebar_ptr->title_pos = 0; - } - - // The 'close' button is shown as long as there's space for title and - // one button, at least. - if (width > title_min_width + - wlmaker_decorations_button_width + - wlmaker_config_theme.window_margin_width) { - titlebar_ptr->close_pos = - width - wlmaker_decorations_button_width; - has_close = true; - } else { - // Won't be shown, but simplifies computation... - titlebar_ptr->close_pos = - width + wlmaker_config_theme.window_margin_width; - has_close = false; - } - - BS_ASSERT( - titlebar_ptr->close_pos >= - (int)wlmaker_config_theme.window_margin_width + titlebar_ptr->title_pos); - titlebar_ptr->title_width = - titlebar_ptr->close_pos - wlmaker_config_theme.window_margin_width - - titlebar_ptr->title_pos; - titlebar_ptr->width = width; - - BS_ASSERT(recreate_backgrounds(titlebar_ptr, width)); - - if (has_minimize) { - create_or_update_minimize_button(titlebar_ptr); - wlmaker_decorations_element_set_position( - wlmaker_decorations_element_from_button( - titlebar_ptr->minimize_button_ptr), - 0, 0); - } else if (NULL != titlebar_ptr->minimize_button_ptr) { - wlmaker_decorations_button_destroy(titlebar_ptr->minimize_button_ptr); - titlebar_ptr->minimize_button_ptr = NULL; - } - - create_or_update_title(titlebar_ptr); - wlmaker_decorations_element_set_position( - wlmaker_decorations_element_from_title(titlebar_ptr->title_ptr), - titlebar_ptr->title_pos, 0); - - if (has_close) { - create_or_update_close_button(titlebar_ptr); - wlmaker_decorations_element_set_position( - wlmaker_decorations_element_from_button( - titlebar_ptr->close_button_ptr), - titlebar_ptr->close_pos, 0); - } else if (NULL != titlebar_ptr->close_button_ptr) { - wlmaker_decorations_button_destroy(titlebar_ptr->close_button_ptr); - titlebar_ptr->close_button_ptr = NULL; - } - - titlebar_ptr->width = width; -} - -/* ------------------------------------------------------------------------- */ -unsigned wlmaker_decorations_titlebar_get_height( - __UNUSED__ wlmaker_decorations_titlebar_t *titlebar_ptr) -{ - return wlmaker_decorations_titlebar_height + - wlmaker_config_theme.window_margin_width; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_decorations_titlebar_update_title( - wlmaker_decorations_titlebar_t *titlebar_ptr) -{ - create_or_update_title(titlebar_ptr); -} - -/* == Local (static) methods =============================================== */ - -/** - * (Re)creates the backgrounds for the title bar. - * - * @param titlebar_ptr - * @param width - * - * @return true on success. - */ -static bool recreate_backgrounds( - wlmaker_decorations_titlebar_t *titlebar_ptr, - unsigned width) -{ - bs_gfxbuf_t *focussed_ptr, *blurred_ptr; - cairo_t *cairo_ptr; - - focussed_ptr = bs_gfxbuf_create( - width, wlmaker_decorations_titlebar_height); - if (NULL == focussed_ptr) return false; - cairo_ptr = cairo_create_from_bs_gfxbuf(focussed_ptr); - if (NULL == cairo_ptr) { - bs_gfxbuf_destroy(focussed_ptr); - return false; - } - wlmaker_primitives_cairo_fill( - cairo_ptr, - &wlmaker_config_theme.titlebar_focussed_fill); - cairo_destroy(cairo_ptr); - - blurred_ptr = bs_gfxbuf_create( - width, wlmaker_decorations_titlebar_height); - if (NULL == blurred_ptr) { - bs_gfxbuf_destroy(focussed_ptr); - return false; - } - cairo_ptr = cairo_create_from_bs_gfxbuf(blurred_ptr); - if (NULL == cairo_ptr) { - bs_gfxbuf_destroy(focussed_ptr); - bs_gfxbuf_destroy(blurred_ptr); - return false; - } - wlmaker_primitives_cairo_fill( - cairo_ptr, - &wlmaker_config_theme.titlebar_blurred_fill); - cairo_destroy(cairo_ptr); - - if (NULL != titlebar_ptr->background_focussed_gfxbuf_ptr) { - bs_gfxbuf_destroy(titlebar_ptr->background_focussed_gfxbuf_ptr); - } - titlebar_ptr->background_focussed_gfxbuf_ptr = focussed_ptr; - if (NULL != titlebar_ptr->background_blurred_gfxbuf_ptr) { - bs_gfxbuf_destroy(titlebar_ptr->background_blurred_gfxbuf_ptr); - } - titlebar_ptr->background_blurred_gfxbuf_ptr = blurred_ptr; - - return true; -} - -/* ------------------------------------------------------------------------- */ -/** Creates WLR buffers of `buffer_holder_ptr`. */ -bool create_wlr_buffers( - wlr_buffer_holder_t *buffer_holder_ptr, - unsigned width, - bool press) -{ - memset(buffer_holder_ptr, 0, sizeof(wlr_buffer_holder_t)); - - buffer_holder_ptr->released_wlrbuf_ptr = bs_gfxbuf_create_wlr_buffer( - width, wlmaker_decorations_titlebar_height); - if (NULL == buffer_holder_ptr->released_wlrbuf_ptr) { - drop_wlr_buffers(buffer_holder_ptr); - return false; - } - - if (press) { - buffer_holder_ptr->pressed_wlrbuf_ptr = bs_gfxbuf_create_wlr_buffer( - width, wlmaker_decorations_titlebar_height); - if (NULL == buffer_holder_ptr->pressed_wlrbuf_ptr) { - drop_wlr_buffers(buffer_holder_ptr); - return false; - } - } - - buffer_holder_ptr->blurred_wlrbuf_ptr = bs_gfxbuf_create_wlr_buffer( - width, wlmaker_decorations_titlebar_height); - if (NULL == buffer_holder_ptr->blurred_wlrbuf_ptr) { - drop_wlr_buffers(buffer_holder_ptr); - return false; - } - - return true; -} - -/* ------------------------------------------------------------------------- */ -/** Drops the WLR buffers of `buffer_holder_ptr`. */ -void drop_wlr_buffers(wlr_buffer_holder_t *buffer_holder_ptr) -{ - if (NULL != buffer_holder_ptr->blurred_wlrbuf_ptr) { - wlr_buffer_drop(buffer_holder_ptr->blurred_wlrbuf_ptr); - buffer_holder_ptr->blurred_wlrbuf_ptr = NULL; - } - - if (NULL != buffer_holder_ptr->pressed_wlrbuf_ptr) { - wlr_buffer_drop(buffer_holder_ptr->pressed_wlrbuf_ptr); - buffer_holder_ptr->pressed_wlrbuf_ptr = NULL; - } - - if (NULL != buffer_holder_ptr->released_wlrbuf_ptr) { - wlr_buffer_drop(buffer_holder_ptr->released_wlrbuf_ptr); - buffer_holder_ptr->released_wlrbuf_ptr = NULL; - } -} - -/* ------------------------------------------------------------------------- */ -/** - * Creates (or updates) the "Minimize" button and textures. - * - * @param titlebar_ptr - */ -void create_or_update_minimize_button( - wlmaker_decorations_titlebar_t *titlebar_ptr) -{ - cairo_t *cairo_ptr; - wlr_buffer_holder_t buf_holder; - - BS_ASSERT(create_wlr_buffers( - &buf_holder, wlmaker_decorations_button_width, true)); - - bs_gfxbuf_copy_area( - bs_gfxbuf_from_wlr_buffer(buf_holder.released_wlrbuf_ptr), - 0, 0, - titlebar_ptr->background_focussed_gfxbuf_ptr, - 0, 0, - wlmaker_decorations_button_width, wlmaker_decorations_titlebar_height); - cairo_ptr = cairo_create_from_wlr_buffer(buf_holder.released_wlrbuf_ptr); - BS_ASSERT(NULL != cairo_ptr); - wlmaker_primitives_draw_bezel( - cairo_ptr, wlmaker_decorations_button_bezel_width, true); - wlmaker_primitives_draw_minimize_icon( - cairo_ptr, wlmaker_config_theme.titlebar_focussed_text_color); - cairo_destroy(cairo_ptr); - - bs_gfxbuf_copy_area( - bs_gfxbuf_from_wlr_buffer(buf_holder.pressed_wlrbuf_ptr), - 0, 0, - titlebar_ptr->background_focussed_gfxbuf_ptr, - 0, 0, - wlmaker_decorations_button_width, wlmaker_decorations_titlebar_height); - cairo_ptr = cairo_create_from_wlr_buffer(buf_holder.pressed_wlrbuf_ptr); - BS_ASSERT(NULL != cairo_ptr); - wlmaker_primitives_draw_bezel( - cairo_ptr, wlmaker_decorations_button_bezel_width, false); - wlmaker_primitives_draw_minimize_icon( - cairo_ptr, wlmaker_config_theme.titlebar_focussed_text_color); - cairo_destroy(cairo_ptr); - - bs_gfxbuf_copy_area( - bs_gfxbuf_from_wlr_buffer(buf_holder.blurred_wlrbuf_ptr), - 0, 0, - titlebar_ptr->background_blurred_gfxbuf_ptr, - 0, 0, - wlmaker_decorations_button_width, wlmaker_decorations_titlebar_height); - cairo_ptr = cairo_create_from_wlr_buffer(buf_holder.blurred_wlrbuf_ptr); - BS_ASSERT(NULL != cairo_ptr); - wlmaker_primitives_draw_bezel( - cairo_ptr, wlmaker_decorations_button_bezel_width, true); - wlmaker_primitives_draw_minimize_icon( - cairo_ptr, wlmaker_config_theme.titlebar_blurred_text_color); - cairo_destroy(cairo_ptr); - - if (NULL == titlebar_ptr->minimize_button_ptr) { - titlebar_ptr->minimize_button_ptr = wlmaker_decorations_button_create( - titlebar_ptr->wlr_scene_tree_ptr, - titlebar_ptr->view_ptr->server_ptr->cursor_ptr, - button_minimize_callback, - titlebar_ptr->view_ptr, - buf_holder.released_wlrbuf_ptr, - buf_holder.pressed_wlrbuf_ptr, - buf_holder.blurred_wlrbuf_ptr, - WLR_EDGE_LEFT | WLR_EDGE_TOP); - BS_ASSERT(NULL != titlebar_ptr->minimize_button_ptr); - } else { - wlmaker_decorations_button_set_textures( - titlebar_ptr->minimize_button_ptr, - buf_holder.released_wlrbuf_ptr, - buf_holder.pressed_wlrbuf_ptr, - buf_holder.blurred_wlrbuf_ptr); - } - - drop_wlr_buffers(&buf_holder); -} - -/* ------------------------------------------------------------------------- */ -/** - * Creates (or updates) the "Close" button and textures. - * - * @param titlebar_ptr - */ -void create_or_update_close_button( - wlmaker_decorations_titlebar_t *titlebar_ptr) -{ - cairo_t *cairo_ptr; - wlr_buffer_holder_t buf_holder; - - BS_ASSERT(create_wlr_buffers( - &buf_holder, wlmaker_decorations_button_width, true)); - - bs_gfxbuf_copy_area( - bs_gfxbuf_from_wlr_buffer(buf_holder.released_wlrbuf_ptr), - 0, 0, - titlebar_ptr->background_focussed_gfxbuf_ptr, - titlebar_ptr->close_pos, 0, - wlmaker_decorations_button_width, wlmaker_decorations_titlebar_height); - cairo_ptr = cairo_create_from_wlr_buffer(buf_holder.released_wlrbuf_ptr); - BS_ASSERT(NULL != cairo_ptr); - wlmaker_primitives_draw_bezel( - cairo_ptr, wlmaker_decorations_button_bezel_width, true); - wlmaker_primitives_draw_close_icon( - cairo_ptr, wlmaker_config_theme.titlebar_focussed_text_color); - cairo_destroy(cairo_ptr); - - bs_gfxbuf_copy_area( - bs_gfxbuf_from_wlr_buffer(buf_holder.pressed_wlrbuf_ptr), - 0, 0, - titlebar_ptr->background_focussed_gfxbuf_ptr, - titlebar_ptr->close_pos, 0, - wlmaker_decorations_button_width, wlmaker_decorations_titlebar_height); - cairo_ptr = cairo_create_from_wlr_buffer(buf_holder.pressed_wlrbuf_ptr); - BS_ASSERT(NULL != cairo_ptr); - wlmaker_primitives_draw_bezel( - cairo_ptr, wlmaker_decorations_button_bezel_width, false); - wlmaker_primitives_draw_close_icon( - cairo_ptr, wlmaker_config_theme.titlebar_focussed_text_color); - cairo_destroy(cairo_ptr); - - bs_gfxbuf_copy_area( - bs_gfxbuf_from_wlr_buffer(buf_holder.blurred_wlrbuf_ptr), - 0, 0, - titlebar_ptr->background_blurred_gfxbuf_ptr, - titlebar_ptr->close_pos, 0, - wlmaker_decorations_button_width, wlmaker_decorations_titlebar_height); - cairo_ptr = cairo_create_from_wlr_buffer(buf_holder.blurred_wlrbuf_ptr); - BS_ASSERT(NULL != cairo_ptr); - wlmaker_primitives_draw_bezel( - cairo_ptr, wlmaker_decorations_button_bezel_width, true); - wlmaker_primitives_draw_close_icon( - cairo_ptr, wlmaker_config_theme.titlebar_blurred_text_color); - cairo_destroy(cairo_ptr); - - if (NULL == titlebar_ptr->close_button_ptr) { - titlebar_ptr->close_button_ptr = wlmaker_decorations_button_create( - titlebar_ptr->wlr_scene_tree_ptr, - titlebar_ptr->view_ptr->server_ptr->cursor_ptr, - button_close_callback, - titlebar_ptr->view_ptr, - buf_holder.released_wlrbuf_ptr, - buf_holder.pressed_wlrbuf_ptr, - buf_holder.blurred_wlrbuf_ptr, - WLR_EDGE_RIGHT | WLR_EDGE_TOP); - BS_ASSERT(NULL != titlebar_ptr->close_button_ptr); - } else { - wlmaker_decorations_button_set_textures( - titlebar_ptr->close_button_ptr, - buf_holder.released_wlrbuf_ptr, - buf_holder.pressed_wlrbuf_ptr, - buf_holder.blurred_wlrbuf_ptr); - } - - drop_wlr_buffers(&buf_holder); -} - -/* ------------------------------------------------------------------------- */ -/** - * Creates (or updates) the title element and textures of the title bar. - * - * @param titlebar_ptr - */ -void create_or_update_title(wlmaker_decorations_titlebar_t *titlebar_ptr) -{ - cairo_t *cairo_ptr; - wlr_buffer_holder_t buf_holder; - - BS_ASSERT(create_wlr_buffers( - &buf_holder, titlebar_ptr->title_width, false)); - - bs_gfxbuf_copy_area( - bs_gfxbuf_from_wlr_buffer(buf_holder.released_wlrbuf_ptr), - 0, 0, - titlebar_ptr->background_focussed_gfxbuf_ptr, - titlebar_ptr->title_pos, 0, - titlebar_ptr->title_width, wlmaker_decorations_titlebar_height); - cairo_ptr = cairo_create_from_wlr_buffer(buf_holder.released_wlrbuf_ptr); - BS_ASSERT(NULL != cairo_ptr); - wlmaker_primitives_draw_bezel( - cairo_ptr, wlmaker_decorations_button_bezel_width, true); - wlmaker_primitives_draw_window_title( - cairo_ptr, - wlmaker_view_get_title(titlebar_ptr->view_ptr), - wlmaker_config_theme.titlebar_focussed_text_color); - cairo_destroy(cairo_ptr); - - bs_gfxbuf_copy_area( - bs_gfxbuf_from_wlr_buffer(buf_holder.blurred_wlrbuf_ptr), - 0, 0, - titlebar_ptr->background_blurred_gfxbuf_ptr, - titlebar_ptr->title_pos, 0, - titlebar_ptr->title_width, wlmaker_decorations_titlebar_height); - cairo_ptr = cairo_create_from_wlr_buffer(buf_holder.blurred_wlrbuf_ptr); - BS_ASSERT(NULL != cairo_ptr); - wlmaker_primitives_draw_bezel( - cairo_ptr, wlmaker_decorations_button_bezel_width, true); - wlmaker_primitives_draw_window_title( - cairo_ptr, - wlmaker_view_get_title(titlebar_ptr->view_ptr), - wlmaker_config_theme.titlebar_blurred_text_color); - cairo_destroy(cairo_ptr); - - if (NULL == titlebar_ptr->title_ptr) { - titlebar_ptr->title_ptr = wlmaker_decorations_title_create( - titlebar_ptr->wlr_scene_tree_ptr, - titlebar_ptr->view_ptr->server_ptr->cursor_ptr, - titlebar_ptr->view_ptr, - buf_holder.released_wlrbuf_ptr, - buf_holder.blurred_wlrbuf_ptr); - BS_ASSERT(NULL != titlebar_ptr->title_ptr); - } else { - wlmaker_decorations_title_set_textures( - titlebar_ptr->title_ptr, - buf_holder.released_wlrbuf_ptr, - buf_holder.blurred_wlrbuf_ptr); - } - - drop_wlr_buffers(&buf_holder); -} - -/* ------------------------------------------------------------------------- */ -/** - * Callback for the "minimize" button action. - * - * @param interactive_ptr Points to the interactive that triggered the - * action. Unused. - * @param data_ptr This view. - */ -void button_minimize_callback( - __UNUSED__ wlmaker_interactive_t *interactive_ptr, - void *data_ptr) -{ - wlmaker_view_t *view_ptr = (wlmaker_view_t*)data_ptr; - wlmaker_view_set_iconified(view_ptr, true); -} - -/* ------------------------------------------------------------------------- */ -/** - * Callback for the close button action. - * - * @param interactive_ptr Points to the interactive that triggered the - * action. Unused. - * @param data_ptr This view. - */ -void button_close_callback( - __UNUSED__ wlmaker_interactive_t *interactive_ptr, - void *data_ptr) -{ - wlmaker_view_t *view_ptr = (wlmaker_view_t*)data_ptr; - view_ptr->send_close_callback(view_ptr); -} - -/* == End of titlebar.c ==================================================== */ diff --git a/src/decorations/titlebar.h b/src/decorations/titlebar.h deleted file mode 100644 index b767c486..00000000 --- a/src/decorations/titlebar.h +++ /dev/null @@ -1,90 +0,0 @@ -/* ========================================================================= */ -/** - * @file titlebar.h - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef __TITLEBAR_H__ -#define __TITLEBAR_H__ - -#define WLR_USE_UNSTABLE -#include -#undef WLR_USE_UNSTABLE - -#include "../view.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** Forward declaration: Titlebar of window decoration. */ -typedef struct _wlmaker_decorations_titlebar_t wlmaker_decorations_titlebar_t; - -/** - * Creates the title bar for window decoration. - * - * @param wlr_scene_tree_ptr - * @param width - * @param view_ptr - * - * @return A titlebar handle, or NULL on error. Must be free-d by calling - * @ref wlmaker_decorations_titlebar_destroy. - */ -wlmaker_decorations_titlebar_t *wlmaker_decorations_titlebar_create( - struct wlr_scene_tree *wlr_scene_tree_ptr, - unsigned width, - wlmaker_view_t *view_ptr); - -/** - * Destroys a window decoration title bar. - * - * @param titlebar_ptr - */ -void wlmaker_decorations_titlebar_destroy( - wlmaker_decorations_titlebar_t *titlebar_ptr); - -/** - * Sets the width of the titlebar. - * - * @param titlebar_ptr - * @param width - */ -void wlmaker_decorations_titlebar_set_width( - wlmaker_decorations_titlebar_t *titlebar_ptr, - unsigned width); - -/** - * Returns the height of the titlebar. Includes the top margin. - * - * @param titlebar_ptr - */ -unsigned wlmaker_decorations_titlebar_get_height( - wlmaker_decorations_titlebar_t *titlebar_ptr); - -/** - * Sets the title of the titlebar. Will pull it from the view. - * - * @param titlebar_ptr - */ -void wlmaker_decorations_titlebar_update_title( - wlmaker_decorations_titlebar_t *titlebar_ptr); - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif /* __TITLEBAR_H__ */ -/* == End of titlebar.h ==================================================== */ diff --git a/src/decorations/window_decorations.c b/src/decorations/window_decorations.c deleted file mode 100644 index dd57f4f8..00000000 --- a/src/decorations/window_decorations.c +++ /dev/null @@ -1,230 +0,0 @@ -/* ========================================================================= */ -/** - * @file window_decorations.c - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "window_decorations.h" - -#include "../config.h" -#include "margin.h" -#include "resizebar.h" -#include "titlebar.h" - -/* == Declarations ========================================================= */ - -/** State of the decoration of a window. */ -struct _wlmaker_window_decorations_t { - /** Back-link to the view. */ - wlmaker_view_t *view_ptr; - - /** Scene tree holding all decoration elements. */ - struct wlr_scene_tree *wlr_scene_tree_ptr; - - /** Window margins. */ - wlmaker_decorations_margin_t *margin_ptr; - - /** The titlebar, including buttons. */ - wlmaker_decorations_titlebar_t *titlebar_ptr; - - /** The resizebar, including all resize elements and margin. */ - wlmaker_decorations_resizebar_t *resizebar_ptr; -}; - -/* == Exported methods ===================================================== */ - -/* ------------------------------------------------------------------------- */ -wlmaker_window_decorations_t *wlmaker_window_decorations_create( - wlmaker_view_t *view_ptr) -{ - wlmaker_window_decorations_t *decorations_ptr = logged_calloc( - 1, sizeof(wlmaker_window_decorations_t)); - if (NULL == decorations_ptr) return NULL; - decorations_ptr->view_ptr = view_ptr; - - // Must be mapped. TODO(kaeser@gubbe.ch): Don't rely on internals! - BS_ASSERT(NULL != view_ptr->workspace_ptr); - BS_ASSERT(view_ptr->server_side_decoration_enabled); - BS_ASSERT(!view_ptr->fullscreen); - // TODO(kaeser@gubbe.ch): Shouldn't need to access the internals. - uint32_t width, height; - view_ptr->impl_ptr->get_size(view_ptr, &width, &height); - - decorations_ptr->wlr_scene_tree_ptr = wlr_scene_tree_create( - view_ptr->elements_wlr_scene_tree_ptr); - if (NULL == decorations_ptr->wlr_scene_tree_ptr) { - wlmaker_window_decorations_destroy(decorations_ptr); - return NULL; - } - - // Margins around the window itself (not including title or resize bar). - decorations_ptr->margin_ptr = wlmaker_decorations_margin_create( - decorations_ptr->wlr_scene_tree_ptr, 0, 0, width, height, - WLR_EDGE_LEFT | WLR_EDGE_TOP | WLR_EDGE_RIGHT | WLR_EDGE_BOTTOM); - if (NULL == decorations_ptr->margin_ptr) { - wlmaker_window_decorations_destroy(decorations_ptr); - return NULL; - } - - decorations_ptr->titlebar_ptr = wlmaker_decorations_titlebar_create( - decorations_ptr->wlr_scene_tree_ptr, width, view_ptr); - if (NULL == decorations_ptr->titlebar_ptr) { - wlmaker_window_decorations_destroy(decorations_ptr); - return NULL; - } - - decorations_ptr->resizebar_ptr = wlmaker_decorations_resizebar_create( - decorations_ptr->wlr_scene_tree_ptr, width, height, view_ptr); - if (NULL == decorations_ptr->resizebar_ptr) { - wlmaker_window_decorations_destroy(decorations_ptr); - return NULL; - } - - - return decorations_ptr; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_window_decorations_destroy( - wlmaker_window_decorations_t *decorations_ptr) -{ - if (NULL != decorations_ptr->resizebar_ptr) { - wlmaker_decorations_resizebar_destroy(decorations_ptr->resizebar_ptr); - decorations_ptr->resizebar_ptr = NULL; - } - - if (NULL != decorations_ptr->titlebar_ptr) { - wlmaker_decorations_titlebar_destroy(decorations_ptr->titlebar_ptr); - decorations_ptr->titlebar_ptr = NULL; - } - - if (NULL != decorations_ptr->margin_ptr) { - wlmaker_decorations_margin_destroy(decorations_ptr->margin_ptr); - decorations_ptr->margin_ptr = NULL; - } - - if (NULL != decorations_ptr->wlr_scene_tree_ptr) { - wlr_scene_node_destroy(&decorations_ptr->wlr_scene_tree_ptr->node); - decorations_ptr->wlr_scene_tree_ptr = NULL; - } - - free(decorations_ptr); -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_window_decorations_set_inner_size( - wlmaker_window_decorations_t *decorations_ptr, - uint32_t width, - uint32_t height) -{ - if (NULL != decorations_ptr->margin_ptr) { - wlmaker_decorations_margin_set_size( - decorations_ptr->margin_ptr, width, height); - } - - if (NULL != decorations_ptr->titlebar_ptr) { - wlmaker_decorations_titlebar_set_width( - decorations_ptr->titlebar_ptr, width); - } - - if (NULL != decorations_ptr->resizebar_ptr) { - wlmaker_decorations_resizebar_set_size( - decorations_ptr->resizebar_ptr, width, height); - } -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_window_decorations_get_added_size( - wlmaker_window_decorations_t *decorations_ptr, - uint32_t *width_ptr, - uint32_t *height_ptr) -{ - if (NULL != width_ptr) { - *width_ptr = 2 * wlmaker_config_theme.window_margin_width; - } - - if (NULL != height_ptr) { - *height_ptr = 2 * wlmaker_config_theme.window_margin_width; - if (NULL != decorations_ptr->titlebar_ptr) { - *height_ptr += wlmaker_decorations_titlebar_get_height( - decorations_ptr->titlebar_ptr); - } - - if (NULL != decorations_ptr->resizebar_ptr) { - *height_ptr += wlmaker_decorations_resizebar_get_height( - decorations_ptr->resizebar_ptr); - } - } -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_window_decorations_relative_position( - wlmaker_window_decorations_t *decorations_ptr, - int *relx_ptr, - int *rely_ptr) -{ - if (NULL != relx_ptr) { - *relx_ptr = -wlmaker_config_theme.window_margin_width; - } - - if (NULL != rely_ptr) { - *rely_ptr = -wlmaker_config_theme.window_margin_width; - if (NULL != decorations_ptr->titlebar_ptr) { - *rely_ptr -= wlmaker_decorations_titlebar_get_height( - decorations_ptr->titlebar_ptr); - } - } -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_window_decorations_update_title( - wlmaker_window_decorations_t *decorations_ptr) -{ - if (NULL != decorations_ptr->titlebar_ptr) { - wlmaker_decorations_titlebar_update_title( - decorations_ptr->titlebar_ptr); - } -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_window_decorations_set_shade( - wlmaker_window_decorations_t *decorations_ptr, - bool shaded) -{ - if (shaded && NULL != decorations_ptr->resizebar_ptr) { - wlmaker_decorations_resizebar_destroy(decorations_ptr->resizebar_ptr); - decorations_ptr->resizebar_ptr = NULL; - } - - if (!shaded && NULL == decorations_ptr->resizebar_ptr) { - uint32_t width, height; - decorations_ptr->view_ptr->impl_ptr->get_size( - decorations_ptr->view_ptr, &width, &height); - decorations_ptr->resizebar_ptr = wlmaker_decorations_resizebar_create( - decorations_ptr->wlr_scene_tree_ptr, - width, height, - decorations_ptr->view_ptr); - } - - wlmaker_decorations_margin_set_edges( - decorations_ptr->margin_ptr, - shaded ? - WLR_EDGE_TOP : - WLR_EDGE_LEFT | WLR_EDGE_TOP | WLR_EDGE_RIGHT | WLR_EDGE_BOTTOM); -} - -/* == End of window_decorations.c ========================================== */ diff --git a/src/decorations/window_decorations.h b/src/decorations/window_decorations.h deleted file mode 100644 index 5c874130..00000000 --- a/src/decorations/window_decorations.h +++ /dev/null @@ -1,125 +0,0 @@ -/* ========================================================================= */ -/** - * @file window_decorations.h - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef __WINDOW_DECORATIONS_H__ -#define __WINDOW_DECORATIONS_H__ - -/** Forward declaration: Handle for decorations for a window. */ -typedef struct _wlmaker_window_decorations_t wlmaker_window_decorations_t; - -#include "../view.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * Creates window decorations for the provided window (view). - * - * Will create a margin, title bar and resize bar. Decorations should only - * be created when the view (1) has decorations enabled, (2) is mapped - * and (3) is not in fullscreen mode. - * - * => TODO: Take flags as to which elements are on. (resizing? - * menu bar elements?) - * - * @param view_ptr - * - * @return A handle for the window's decorations. Must be free'd by calling - * @ref wlmaker_window_decorations_destroy(). - */ -wlmaker_window_decorations_t *wlmaker_window_decorations_create( - wlmaker_view_t *view_ptr); - -/** - * Destroys the window decorations. - * - * @param decorations_ptr - */ -void wlmaker_window_decorations_destroy( - wlmaker_window_decorations_t *decorations_ptr); - -/** - * Sets (or updates) the size of the decorated (inner) window. - * - * `width` and `height` are specifying the dimensions of the decorated window, - * ie. without the added size of the decorations. - * - * @param decorations_ptr - * @param width - * @param height - */ -void wlmaker_window_decorations_set_inner_size( - wlmaker_window_decorations_t *decorations_ptr, - uint32_t width, - uint32_t height); - -/** - * Retrieves the size added by the decoration. - * - * @param decorations_ptr - * @param width_ptr - * @param height_ptr - */ -void wlmaker_window_decorations_get_added_size( - wlmaker_window_decorations_t *decorations_ptr, - uint32_t *width_ptr, - uint32_t *height_ptr); - -/** - * Returns the relative position of the decoration to the inner window. - * - * The top-left corner of the decoration of an inner window is placed at - * `x-position of inner window` plus `*relx_ptr`. Same for Y position. - * - * @param decorations_ptr - * @param relx_ptr - * @param rely_ptr - */ -void wlmaker_window_decorations_relative_position( - wlmaker_window_decorations_t *decorations_ptr, - int *relx_ptr, - int *rely_ptr); - -/** - * Updates the title used for the windo decoration. Wraps to titlebar. - * - * @param decorations_ptr - */ -void wlmaker_window_decorations_update_title( - wlmaker_window_decorations_t *decorations_ptr); - -/** - * Sets the "shade" status for decorations. When shaded, the resizebar is - * hidden. - * - * @param decorations_ptr - * @param shaded - */ -void wlmaker_window_decorations_set_shade( - wlmaker_window_decorations_t *decorations_ptr, - bool shaded); - - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif /* __WINDOW_DECORATIONS_H__ */ -/* == End of window_decorations.h ========================================== */ diff --git a/src/dock.c b/src/dock.c index 8b72011e..d32e06fb 100644 --- a/src/dock.c +++ b/src/dock.c @@ -22,7 +22,7 @@ #include "config.h" #include "dock_app.h" -#include "util.h" +#include "toolkit/toolkit.h" #include "view.h" /* == Declarations ========================================================= */ @@ -129,7 +129,7 @@ wlmaker_dock_t *wlmaker_dock_create( } } - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->workspace_changed, &dock_ptr->workspace_changed_listener, handle_workspace_changed); diff --git a/src/icon_manager.c b/src/icon_manager.c index c49b2bad..30c9d788 100644 --- a/src/icon_manager.c +++ b/src/icon_manager.c @@ -26,7 +26,7 @@ #include #undef WLR_USE_UNSTABLE -#include "util.h" +#include "toolkit/toolkit.h" #include "wlmaker-icon-unstable-v1-server-protocol.h" /* == Declarations ========================================================= */ @@ -345,7 +345,7 @@ wlmaker_toplevel_icon_t *wlmaker_toplevel_icon_create( toplevel_icon_ptr, toplevel_icon_resource_destroy); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &toplevel_icon_ptr->wlr_surface_ptr->events.commit, &toplevel_icon_ptr->surface_commit_listener, handle_surface_commit); diff --git a/src/iconified.c b/src/iconified.c index 951e053b..cb3caedd 100644 --- a/src/iconified.c +++ b/src/iconified.c @@ -118,8 +118,8 @@ wlmaker_dockapp_iconified_t *wlmaker_dockapp_iconified_create( return NULL; } - const wlmaker_style_fill_t fill = { - .type = WLMAKER_STYLE_COLOR_DGRADIENT, + const wlmtk_style_fill_t fill = { + .type = WLMTK_STYLE_COLOR_DGRADIENT, .param = { .hgradient = { .from = 0xff767686,.to = 0xff313541 }} }; wlmaker_decorations_draw_tile(cairo_ptr, &fill, false); diff --git a/src/keyboard.c b/src/keyboard.c index 8df2f301..f3680fbc 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -21,7 +21,7 @@ #include "keyboard.h" #include "config.h" -#include "util.h" +#include "toolkit/toolkit.h" #include "server.h" /* == Declarations ========================================================= */ @@ -79,11 +79,11 @@ wlmaker_keyboard_t *wlmaker_keyboard_create( config_keyboard_repeat_rate, config_keyboard_repeat_delay); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &keyboard_ptr->wlr_keyboard_ptr->events.key, &keyboard_ptr->key_listener, handle_key); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &keyboard_ptr->wlr_keyboard_ptr->events.modifiers, &keyboard_ptr->modifiers_listener, handle_modifiers); diff --git a/src/layer_shell.c b/src/layer_shell.c index 8653fe13..d317f533 100644 --- a/src/layer_shell.c +++ b/src/layer_shell.c @@ -21,7 +21,7 @@ #include "layer_shell.h" #include "layer_surface.h" -#include "util.h" +#include "toolkit/toolkit.h" #include @@ -69,11 +69,11 @@ wlmaker_layer_shell_t *wlmaker_layer_shell_create(wlmaker_server_t *server_ptr) return NULL; } - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &layer_shell_ptr->wlr_layer_shell_v1_ptr->events.new_surface, &layer_shell_ptr->new_surface_listener, handle_new_surface); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &layer_shell_ptr->wlr_layer_shell_v1_ptr->events.destroy, &layer_shell_ptr->destroy_listener, handle_destroy); diff --git a/src/layer_surface.c b/src/layer_surface.c index e1792f63..e02f2d85 100644 --- a/src/layer_surface.c +++ b/src/layer_surface.c @@ -20,7 +20,7 @@ #include "layer_surface.h" -#include "util.h" +#include "toolkit/toolkit.h" #include "view.h" #include "xdg_popup.h" @@ -109,25 +109,25 @@ wlmaker_layer_surface_t *wlmaker_layer_surface_create( return NULL; } - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_layer_surface_v1_ptr->events.destroy, &layer_surface_ptr->destroy_listener, handle_destroy); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_layer_surface_v1_ptr->events.new_popup, &layer_surface_ptr->new_popup_listener, handle_new_popup); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_layer_surface_v1_ptr->surface->events.map, &layer_surface_ptr->surface_map_listener, handle_map); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_layer_surface_v1_ptr->surface->events.unmap, &layer_surface_ptr->surface_unmap_listener, handle_unmap); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_layer_surface_v1_ptr->surface->events.commit, &layer_surface_ptr->surface_commit_listener, handle_surface_commit); diff --git a/src/menu.c b/src/menu.c index abe8161f..e7a1dd35 100644 --- a/src/menu.c +++ b/src/menu.c @@ -181,10 +181,10 @@ wlmaker_menu_t *menu_from_interactive( void _menu_enter( wlmaker_interactive_t *interactive_ptr) { - wlr_xcursor_manager_set_cursor_image( + wlr_cursor_set_xcursor( + interactive_ptr->cursor_ptr->wlr_cursor_ptr, interactive_ptr->cursor_ptr->wlr_xcursor_manager_ptr, - "left_ptr", - interactive_ptr->cursor_ptr->wlr_cursor_ptr); + "left_ptr"); } /* ------------------------------------------------------------------------- */ diff --git a/src/menu_item.c b/src/menu_item.c index 45ddc552..b22de976 100644 --- a/src/menu_item.c +++ b/src/menu_item.c @@ -128,7 +128,7 @@ void wlmaker_menu_item_draw( cairo_ptr, menu_item_ptr->x, menu_item_ptr->y, menu_item_ptr->width, menu_item_ptr->height, 1.0, true); - const wlmaker_style_fill_t *fill_ptr = NULL; + const wlmtk_style_fill_t *fill_ptr = NULL; uint32_t text_color = 0; switch (menu_item_ptr->state) { case WLMAKER_MENU_ITEM_STATE_ENABLED: @@ -244,8 +244,8 @@ static const wlmaker_menu_item_descriptor_t test_descriptor = { }; /** Properties of the fill, used for the unit test. */ -static const wlmaker_style_fill_t test_fill = { - .type = WLMAKER_STYLE_COLOR_DGRADIENT, +static const wlmtk_style_fill_t test_fill = { + .type = WLMTK_STYLE_COLOR_DGRADIENT, .param = { .hgradient = { .from = 0xffa6a6b6,.to = 0xff515561 }} }; diff --git a/src/output.c b/src/output.c index 6dcf7ed8..07c0832a 100644 --- a/src/output.c +++ b/src/output.c @@ -23,7 +23,7 @@ #include "output.h" -#include "util.h" +#include "toolkit/toolkit.h" #include @@ -54,15 +54,15 @@ wlmaker_output_t *wlmaker_output_create( output_ptr->wlr_scene_ptr = wlr_scene_ptr; output_ptr->server_ptr = server_ptr; - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &output_ptr->wlr_output_ptr->events.destroy, &output_ptr->output_destroy_listener, handle_output_destroy); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &output_ptr->wlr_output_ptr->events.frame, &output_ptr->output_frame_listener, handle_output_frame); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &output_ptr->wlr_output_ptr->events.request_state, &output_ptr->output_request_state_listener, handle_request_state); @@ -140,7 +140,7 @@ void handle_output_frame(struct wl_listener *listener_ptr, struct wlr_scene_output *wlr_scene_output_ptr = wlr_scene_get_scene_output( output_ptr->wlr_scene_ptr, output_ptr->wlr_output_ptr); - wlr_scene_output_commit(wlr_scene_output_ptr); + wlr_scene_output_commit(wlr_scene_output_ptr, NULL); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); diff --git a/src/resizebar.c b/src/resizebar.c deleted file mode 100644 index 845ab543..00000000 --- a/src/resizebar.c +++ /dev/null @@ -1,278 +0,0 @@ -/* ========================================================================= */ -/** - * @file resizebar.c - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "resizebar.h" - -#include -#include - -#define WLR_USE_UNSTABLE -#include -#undef WLR_USE_UNSTABLE - -/* == Declarations ========================================================= */ - -/** State of an interactive resizebar element. */ -typedef struct { - /** The interactive (parent structure). */ - wlmaker_interactive_t interactive; - - /** Back-link to the view. */ - wlmaker_view_t *view_ptr; - /** Texture of the resize bar, not pressed. */ - struct wlr_buffer *resizebar_buffer_ptr; - /** Texture of the resize bar, pressed. */ - struct wlr_buffer *resizebar_pressed_buffer_ptr; - /** Which edges will be controlled by this element. */ - uint32_t edges; - - /** Status of the element. */ - bool pressed; -} wlmaker_resizebar_t; - -static wlmaker_resizebar_t *resizebar_from_interactive( - wlmaker_interactive_t *interactive_ptr); - -static void _resizebar_enter( - wlmaker_interactive_t *interactive_ptr); -static void _resizebar_leave( - wlmaker_interactive_t *interactive_ptr); -static void _resizebar_motion( - wlmaker_interactive_t *interactive_ptr, - double x, - double y); -static void _resizebar_button( - wlmaker_interactive_t *interactive_ptr, - double x, double y, - struct wlr_pointer_button_event *wlr_pointer_button_event_ptr); -static void _resizebar_destroy(wlmaker_interactive_t *interactive_ptr); - -/* == Data ================================================================= */ - -/** Implementation: callbacks for the interactive. */ -static const wlmaker_interactive_impl_t wlmaker_interactive_resizebar_impl = { - .enter = _resizebar_enter, - .leave = _resizebar_leave, - .motion = _resizebar_motion, - .button = _resizebar_button, - .destroy = _resizebar_destroy -}; - -/* == Exported methods ===================================================== */ - -/* ------------------------------------------------------------------------- */ -wlmaker_interactive_t *wlmaker_resizebar_create( - struct wlr_scene_buffer *wlr_scene_buffer_ptr, - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr, - struct wlr_buffer *resizebar_buffer_ptr, - struct wlr_buffer *resizebar_pressed_buffer_ptr, - uint32_t edges) -{ - wlmaker_resizebar_t *resizebar_ptr = logged_calloc( - 1, sizeof(wlmaker_resizebar_t)); - if (NULL == resizebar_ptr) return NULL; - resizebar_ptr->view_ptr = view_ptr; - resizebar_ptr->resizebar_buffer_ptr = - wlr_buffer_lock(resizebar_buffer_ptr); - resizebar_ptr->resizebar_pressed_buffer_ptr = - wlr_buffer_lock(resizebar_pressed_buffer_ptr); - resizebar_ptr->edges = edges; - - wlmaker_interactive_init( - &resizebar_ptr->interactive, - &wlmaker_interactive_resizebar_impl, - wlr_scene_buffer_ptr, - cursor_ptr, - resizebar_buffer_ptr); - - return &resizebar_ptr->interactive; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_resizebar_set_textures( - wlmaker_interactive_t *interactive_ptr, - struct wlr_buffer *resizebar_buffer_ptr, - struct wlr_buffer *resizebar_pressed_buffer_ptr) -{ - wlmaker_resizebar_t *resizebar_ptr = resizebar_from_interactive( - interactive_ptr); - - // This only updates the internal references... - wlr_buffer_unlock(resizebar_ptr->resizebar_buffer_ptr); - resizebar_ptr->resizebar_buffer_ptr = - wlr_buffer_lock(resizebar_buffer_ptr); - wlr_buffer_unlock(resizebar_ptr->resizebar_pressed_buffer_ptr); - resizebar_ptr->resizebar_pressed_buffer_ptr = - wlr_buffer_lock(resizebar_pressed_buffer_ptr); - - // ... and we also need to set the current texture in appropriate state. - wlmaker_interactive_set_texture( - interactive_ptr, - resizebar_ptr->pressed ? - resizebar_ptr->resizebar_pressed_buffer_ptr : - resizebar_ptr->resizebar_buffer_ptr); -} - -/* == Local (static) methods =============================================== */ - -/* ------------------------------------------------------------------------- */ -/** - * Cast (with assertion) |interactive_ptr| to the |wlmaker_resizebar_t|. - * - * @param interactive_ptr - */ -wlmaker_resizebar_t *resizebar_from_interactive( - wlmaker_interactive_t *interactive_ptr) -{ - if (NULL != interactive_ptr && - interactive_ptr->impl != &wlmaker_interactive_resizebar_impl) { - bs_log(BS_FATAL, "Not a resizebar: %p", interactive_ptr); - abort(); - } - return (wlmaker_resizebar_t*)interactive_ptr; -} - -/* ------------------------------------------------------------------------- */ -/** - * Interactive callback: Cursor enters the resizebar area. - * - * @param interactive_ptr - */ -void _resizebar_enter( - wlmaker_interactive_t *interactive_ptr) -{ - wlmaker_resizebar_t *resizebar_ptr = resizebar_from_interactive( - interactive_ptr); - - const char *xcursor_name_ptr = "left_ptr"; // Default. - if (resizebar_ptr->edges == WLR_EDGE_BOTTOM) { - xcursor_name_ptr = "s-resize"; - } else if (resizebar_ptr->edges == (WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT)) { - xcursor_name_ptr = "se-resize"; - } else if (resizebar_ptr->edges == (WLR_EDGE_BOTTOM | WLR_EDGE_LEFT)) { - xcursor_name_ptr = "sw-resize"; - } - - wlr_xcursor_manager_set_cursor_image( - interactive_ptr->cursor_ptr->wlr_xcursor_manager_ptr, - xcursor_name_ptr, - interactive_ptr->cursor_ptr->wlr_cursor_ptr); -} - -/* ------------------------------------------------------------------------- */ -/** - * Interactive callback: Cursor leaves the resizebar area. - * - * @param interactive_ptr - */ -void _resizebar_leave( - __UNUSED__ wlmaker_interactive_t *interactive_ptr) -{ - // Nothing to do. -} - -/* ------------------------------------------------------------------------- */ -/** - * Interactive callback: Cursor motion in the resizebar area. - * - * - * @param interactive_ptr - * @param x - * @param y - */ -void _resizebar_motion( - __UNUSED__ wlmaker_interactive_t *interactive_ptr, - __UNUSED__ double x, - __UNUSED__ double y) -{ - // Nothing to do. -} - -/* ------------------------------------------------------------------------- */ -/** - * Interactive callback: Handle cursor button, ie. button press or release. - * - * @param interactive_ptr - * @param x - * @param y - * @param wlr_pointer_button_event_ptr - */ -void _resizebar_button( - wlmaker_interactive_t *interactive_ptr, - double x, double y, - struct wlr_pointer_button_event *wlr_pointer_button_event_ptr) -{ - wlmaker_resizebar_t *resizebar_ptr = resizebar_from_interactive( - interactive_ptr); - - if (wlr_pointer_button_event_ptr->button != BTN_LEFT) return; - switch (wlr_pointer_button_event_ptr->state) { - case WLR_BUTTON_PRESSED: - if (wlmaker_interactive_contains(&resizebar_ptr->interactive, x, y)) { - resizebar_ptr->pressed = true; - - wlmaker_cursor_begin_resize( - resizebar_ptr->interactive.cursor_ptr, - resizebar_ptr->view_ptr, - resizebar_ptr->edges); - } - break; - - case WLR_BUTTON_RELEASED: - resizebar_ptr->pressed = false; - break; - - default: - /* huh, that's unexpected... */ - break; - } - - wlmaker_interactive_set_texture( - interactive_ptr, - resizebar_ptr->pressed ? - resizebar_ptr->resizebar_pressed_buffer_ptr : - resizebar_ptr->resizebar_buffer_ptr); -} - -/* ------------------------------------------------------------------------- */ -/** - * Destroys resizebar interactive. - * - * @param interactive_ptr - */ -void _resizebar_destroy(wlmaker_interactive_t *interactive_ptr) -{ - wlmaker_resizebar_t *resizebar_ptr = resizebar_from_interactive( - interactive_ptr); - - if (NULL != resizebar_ptr->resizebar_buffer_ptr) { - wlr_buffer_unlock(resizebar_ptr->resizebar_buffer_ptr); - resizebar_ptr->resizebar_buffer_ptr = NULL; - } - if (NULL != resizebar_ptr->resizebar_pressed_buffer_ptr) { - wlr_buffer_unlock(resizebar_ptr->resizebar_pressed_buffer_ptr); - resizebar_ptr->resizebar_pressed_buffer_ptr = NULL; - } - - free(resizebar_ptr); -} - -/* == End of resizebar.c =================================================== */ diff --git a/src/resizebar.h b/src/resizebar.h deleted file mode 100644 index 8e16021e..00000000 --- a/src/resizebar.h +++ /dev/null @@ -1,77 +0,0 @@ -/* ========================================================================= */ -/** - * @file resizebar.h - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef __RESIZEBAR_H__ -#define __RESIZEBAR_H__ - -#include "cursor.h" -#include "interactive.h" - -#define WLR_USE_UNSTABLE -#include -#include -#undef WLR_USE_UNSTABLE - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * Creates a resizebar interactive. - * - * @param wlr_scene_buffer_ptr Buffer scene node to contain the button. - * @param cursor_ptr Cursor. Must outlive the resizebar. - * @param view_ptr View owning the resizebar. Must outlive this. - * @param resizebar_buffer_ptr WLR buffer, resize bar texture. This resizebar - * interactive will hold a consumer lock on it. - * @param resizebar_pressed_buffer_ptr WLR buffer, resize bar texture when - * pressed. - * @param edges Edges that are controlled by this element. - * - * @return A pointer to the interactive. Must be destroyed via - * |_resizebar_destroy|. - */ -wlmaker_interactive_t *wlmaker_resizebar_create( - struct wlr_scene_buffer *wlr_scene_buffer_ptr, - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr, - struct wlr_buffer *resizebar_buffer_ptr, - struct wlr_buffer *resizebar_pressed_buffer_ptr, - uint32_t edges); - -/** - * Sets (replaces) the textures for the resizebar interactive. - * - * @param interactive_ptr - * @param resizebar_buffer_ptr WLR buffer, resize bar texture. This resizebar - * interactive will hold a consumer lock on it. - * @param resizebar_pressed_buffer_ptr WLR buffer, resize bar texture when - * pressed. - */ -void wlmaker_resizebar_set_textures( - wlmaker_interactive_t *interactive_ptr, - struct wlr_buffer *resizebar_buffer_ptr, - struct wlr_buffer *resizebar_pressed_buffer_ptr); - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif /* __RESIZEBAR_H__ */ -/* == End of resizebar.h =================================================== */ diff --git a/src/server.c b/src/server.c index 443d2fbb..2196abd9 100644 --- a/src/server.c +++ b/src/server.c @@ -22,7 +22,7 @@ #include "config.h" #include "output.h" -#include "util.h" +#include "toolkit/toolkit.h" #include @@ -83,6 +83,9 @@ static void handle_destroy_input_device( static void handle_output_layout_change( struct wl_listener *listener_ptr, void *data_ptr); +static void set_extents( + bs_dllist_node_t *dlnode_ptr, + void *ud_ptr); static void arrange_views( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); @@ -141,11 +144,11 @@ wlmaker_server_t *wlmaker_server_create(void) } // Listen for new (or newly recognized) output and input devices. - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->wlr_backend_ptr->events.new_output, &server_ptr->backend_new_output_listener, handle_new_output); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->wlr_backend_ptr->events.new_input, &server_ptr->backend_new_input_device_listener, handle_new_input_device); @@ -181,7 +184,7 @@ wlmaker_server_t *wlmaker_server_create(void) wlmaker_server_destroy(server_ptr); return NULL; } - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->wlr_output_layout_ptr->events.change, &server_ptr->output_layout_change_listener, handle_output_layout_change); @@ -193,8 +196,10 @@ wlmaker_server_t *wlmaker_server_create(void) wlmaker_server_destroy(server_ptr); return NULL; } - if (!wlr_scene_attach_output_layout(server_ptr->wlr_scene_ptr, - server_ptr->wlr_output_layout_ptr)) { + server_ptr->wlr_scene_output_layout_ptr = wlr_scene_attach_output_layout( + server_ptr->wlr_scene_ptr, + server_ptr->wlr_output_layout_ptr); + if (NULL == server_ptr->wlr_scene_output_layout_ptr) { bs_log(BS_ERROR, "Failed wlr_scene_attach_output_layout()"); wlmaker_server_destroy(server_ptr); return NULL; @@ -214,6 +219,15 @@ wlmaker_server_t *wlmaker_server_create(void) return NULL; } + server_ptr->env_ptr = wlmtk_env_create( + server_ptr->cursor_ptr->wlr_cursor_ptr, + server_ptr->cursor_ptr->wlr_xcursor_manager_ptr, + server_ptr->wlr_seat_ptr); + if (NULL == server_ptr->env_ptr) { + wlmaker_server_destroy(server_ptr); + return NULL; + } + // TODO(kaeser@gubbe.ch): Create the workspaces depending on configuration. int workspace_idx = 0; const wlmaker_config_workspace_t *workspace_config_ptr; @@ -360,6 +374,11 @@ void wlmaker_server_destroy(wlmaker_server_t *server_ptr) wlmaker_workspace_destroy(wlmaker_workspace_from_dlnode(dlnode_ptr)); } + if (NULL != server_ptr->env_ptr) { + wlmtk_env_destroy(server_ptr->env_ptr); + server_ptr->env_ptr = NULL; + } + if (NULL != server_ptr->cursor_ptr) { wlmaker_cursor_destroy(server_ptr->cursor_ptr); server_ptr->cursor_ptr = NULL; @@ -402,8 +421,16 @@ void wlmaker_server_output_add(wlmaker_server_t *server_ptr, // outputs from left-to-right in the order they appear. A sophisticated // compositor would let the user configure the arrangement of outputs in // the layout. - wlr_output_layout_add_auto(server_ptr->wlr_output_layout_ptr, - output_ptr->wlr_output_ptr); + struct wlr_output_layout_output *wlr_output_layout_output_ptr = + wlr_output_layout_add_auto(server_ptr->wlr_output_layout_ptr, + output_ptr->wlr_output_ptr); + struct wlr_scene_output *wlr_scene_output_ptr = + wlr_scene_output_create(server_ptr->wlr_scene_ptr, + output_ptr->wlr_output_ptr); + wlr_scene_output_layout_add_output( + server_ptr->wlr_scene_output_layout_ptr, + wlr_output_layout_output_ptr, + wlr_scene_output_ptr); bs_dllist_push_back(&server_ptr->outputs, &output_ptr->node); } @@ -554,7 +581,7 @@ bool register_input_device(wlmaker_server_t *server_ptr, input_device_ptr->wlr_input_device_ptr = wlr_input_device_ptr; input_device_ptr->handle_ptr = handle_ptr; - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_input_device_ptr->events.destroy, &input_device_ptr->destroy_listener, handle_destroy_input_device); @@ -703,9 +730,24 @@ void handle_output_layout_change( bs_log(BS_INFO, "Output layout change: Pos %d, %d (%d x %d).", extents.x, extents.y, extents.width, extents.height); + bs_dllist_for_each(&server_ptr->workspaces, set_extents, &extents); bs_dllist_for_each(&server_ptr->workspaces, arrange_views, NULL); } +/* ------------------------------------------------------------------------- */ +/** + * Callback for `bs_dllist_for_each` to set extents of the workspace. + * + * @param dlnode_ptr + * @param ud_ptr + */ +void set_extents(bs_dllist_node_t *dlnode_ptr, void *ud_ptr) +{ + struct wlr_box *extents_ptr = ud_ptr; + wlmaker_workspace_set_extents( + wlmaker_workspace_from_dlnode(dlnode_ptr), extents_ptr); +} + /* ------------------------------------------------------------------------- */ /** * Callback for `bs_dllist_for_each` to arrange views in a workspace. diff --git a/src/server.h b/src/server.h index a1b14081..91de2968 100644 --- a/src/server.h +++ b/src/server.h @@ -49,6 +49,8 @@ typedef struct _wlmaker_server_t wlmaker_server_t; #include "xdg_shell.h" #include "workspace.h" +#include "toolkit/toolkit.h" + #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -72,6 +74,8 @@ struct _wlmaker_server_t { struct wlr_seat *wlr_seat_ptr; /** The scene graph API. */ struct wlr_scene *wlr_scene_ptr; + /** The scene output layout. */ + struct wlr_scene_output_layout *wlr_scene_output_layout_ptr; /** * Another scene graph, not connected to any output. * @@ -117,6 +121,9 @@ struct _wlmaker_server_t { /** The list of input devices. */ bs_dllist_t input_devices; + /** Toolkit environment. */ + wlmtk_env_t *env_ptr; + /** The current workspace. */ wlmaker_workspace_t *current_workspace_ptr; /** List of all workspaces. */ diff --git a/src/subprocess_monitor.c b/src/subprocess_monitor.c index 84bc1ae1..6176ce43 100644 --- a/src/subprocess_monitor.c +++ b/src/subprocess_monitor.c @@ -20,7 +20,7 @@ #include "subprocess_monitor.h" -#include "util.h" +#include "toolkit/toolkit.h" #include #include @@ -133,19 +133,19 @@ wlmaker_subprocess_monitor_t* wlmaker_subprocess_monitor_create( handle_sigchld, monitor_ptr); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->view_created_event, &monitor_ptr->view_created_listener, handle_view_created); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->view_mapped_event, &monitor_ptr->view_mapped_listener, handle_view_mapped); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->view_unmapped_event, &monitor_ptr->view_unmapped_listener, handle_view_unmapped); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->view_destroyed_event, &monitor_ptr->view_destroyed_listener, handle_view_destroyed); diff --git a/src/task_list.c b/src/task_list.c index 1e692bbb..b98f6857 100644 --- a/src/task_list.c +++ b/src/task_list.c @@ -24,7 +24,7 @@ #include "task_list.h" #include "config.h" -#include "util.h" +#include "toolkit/toolkit.h" #include #include @@ -144,19 +144,19 @@ wlmaker_task_list_t *wlmaker_task_list_create( task_list_ptr->wlr_scene_tree_ptr, NULL); // send_close_callback. - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->task_list_enabled_event, &task_list_ptr->task_list_enabled_listener, handle_task_list_enabled); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->task_list_disabled_event, &task_list_ptr->task_list_disabled_listener, handle_task_list_disabled); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->view_mapped_event, &task_list_ptr->view_mapped_listener, handle_view_mapped); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &server_ptr->view_unmapped_event, &task_list_ptr->view_unmapped_listener, handle_view_unmapped); diff --git a/src/tile_container.c b/src/tile_container.c index 9cf5f853..075dd833 100644 --- a/src/tile_container.c +++ b/src/tile_container.c @@ -120,7 +120,6 @@ void wlmaker_tile_container_add( wlmaker_iconified_t *iconified_ptr) { bs_dllist_node_t *dlnode_ptr = wlmaker_dlnode_from_iconified(iconified_ptr); - BS_ASSERT(bs_dllist_node_orphaned(dlnode_ptr)); bs_dllist_push_back(&tile_container_ptr->tiles, dlnode_ptr); struct wlr_scene_node *wlr_scene_node_ptr = diff --git a/src/titlebar.c b/src/titlebar.c deleted file mode 100644 index 41c63ca3..00000000 --- a/src/titlebar.c +++ /dev/null @@ -1,343 +0,0 @@ -/* ========================================================================= */ -/** - * @file titlebar.c - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "config.h" -#include "titlebar.h" - -#include -#include - -#define WLR_USE_UNSTABLE -#include -#include -#undef WLR_USE_UNSTABLE - -/* == Declarations ========================================================= */ - -/** Titlebar state, with respect to moves. */ -typedef enum { - /** Idle */ - TITLEBAR_IDLE, - /** Clicked, waiting to initiate move. */ - TITLEBAR_CLICKED, - /** Actively moving. */ - TITLEBAR_MOVING -} titlebar_state_t; - -/** State of the interactive titlebar. */ -typedef struct { - /** The interactive (parent structure). */ - wlmaker_interactive_t interactive; - - /** Back-link to the view owning this titlebar. */ - wlmaker_view_t *view_ptr; - - /** WLR buffer, contains texture for the title bar when focussed. */ - struct wlr_buffer *titlebar_buffer_ptr; - /** WLR buffer, contains texture for the title bar when blurred. */ - struct wlr_buffer *titlebar_blurred_buffer_ptr; - - /** Titlebar state. */ - titlebar_state_t state; - /** X-Position of where the click happened. */ - double clicked_x; - /** Y-Position of where the click happened. */ - double clicked_y; - - /** Nanosecond of last mouse-click, to catch double-clicks. */ - uint64_t last_click_nsec; -} wlmaker_titlebar_t; - -static wlmaker_titlebar_t *titlebar_from_interactive( - wlmaker_interactive_t *interactive_ptr); - -static void _titlebar_enter( - wlmaker_interactive_t *interactive_ptr); -static void _titlebar_leave( - wlmaker_interactive_t *interactive_ptr); -static void _titlebar_motion( - wlmaker_interactive_t *interactive_ptr, - double x, double y); -static void _titlebar_focus( - wlmaker_interactive_t *interactive_ptr); -static void _titlebar_button( - wlmaker_interactive_t *interactive_ptr, - double x, double y, - struct wlr_pointer_button_event *wlr_pointer_button_event_ptr); -static void _titlebar_destroy( - wlmaker_interactive_t *interactive_ptr); - -/* == Data ================================================================= */ - -/** Implementation: callbacks for the interactive. */ -static const wlmaker_interactive_impl_t wlmaker_interactive_titlebar_impl = { - .enter = _titlebar_enter, - .leave = _titlebar_leave, - .motion = _titlebar_motion, - .focus = _titlebar_focus, - .button = _titlebar_button, - .destroy = _titlebar_destroy -}; - -/** Default xcursor to use. */ -static const char *xcursor_name_default = "left_ptr"; -/** Xcursor to show when in MOVING state. */ -static const char *xcursor_name_move = "move"; -/** Minimum cursor move to enable MOVING afer CLICKED. */ -static const double minimal_move = 2; - -/* == Exported methods ===================================================== */ - -/* ------------------------------------------------------------------------- */ -wlmaker_interactive_t *wlmaker_titlebar_create( - struct wlr_scene_buffer *wlr_scene_buffer_ptr, - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr, - struct wlr_buffer *titlebar_buffer_ptr, - struct wlr_buffer *titlebar_blurred_buffer_ptr) -{ - wlmaker_titlebar_t *titlebar_ptr = logged_calloc( - 1, sizeof(wlmaker_titlebar_t)); - if (NULL == titlebar_ptr) return NULL; - titlebar_ptr->view_ptr = view_ptr; - titlebar_ptr->titlebar_buffer_ptr = wlr_buffer_lock(titlebar_buffer_ptr); - titlebar_ptr->titlebar_blurred_buffer_ptr = - wlr_buffer_lock(titlebar_blurred_buffer_ptr); - titlebar_ptr->state = TITLEBAR_IDLE; - - wlmaker_interactive_init( - &titlebar_ptr->interactive, - &wlmaker_interactive_titlebar_impl, - wlr_scene_buffer_ptr, - cursor_ptr, - titlebar_buffer_ptr); - - return &titlebar_ptr->interactive; -} - -/* ------------------------------------------------------------------------- */ -void wlmaker_title_set_texture( - wlmaker_interactive_t *interactive_ptr, - struct wlr_buffer *titlebar_buffer_ptr, - struct wlr_buffer *titlebar_blurred_buffer_ptr) -{ - wlmaker_titlebar_t *titlebar_ptr = titlebar_from_interactive( - interactive_ptr); - wlr_buffer_unlock(titlebar_ptr->titlebar_buffer_ptr); - wlr_buffer_unlock(titlebar_ptr->titlebar_blurred_buffer_ptr); - titlebar_ptr->titlebar_buffer_ptr = wlr_buffer_lock(titlebar_buffer_ptr); - titlebar_ptr->titlebar_blurred_buffer_ptr = - wlr_buffer_lock(titlebar_blurred_buffer_ptr); - wlmaker_interactive_set_texture( - interactive_ptr, - interactive_ptr->focussed ? - titlebar_ptr->titlebar_buffer_ptr : - titlebar_ptr->titlebar_blurred_buffer_ptr); -} - -/* == Local (static) methods =============================================== */ - -/* ------------------------------------------------------------------------- */ -/** - * Cast (with assertion) |interactive_ptr| to the |wlmaker_titlebar_t|. - * - * @param interactive_ptr - */ -wlmaker_titlebar_t *titlebar_from_interactive( - wlmaker_interactive_t *interactive_ptr) -{ - if (NULL != interactive_ptr && - interactive_ptr->impl != &wlmaker_interactive_titlebar_impl) { - bs_log(BS_FATAL, "Not a titlebar: %p", interactive_ptr); - abort(); - } - return (wlmaker_titlebar_t*)interactive_ptr; -} - -/* ------------------------------------------------------------------------- */ -/** - * Interactive callback: Cursor enters the button area. - * - * @param interactive_ptr - */ -void _titlebar_enter(wlmaker_interactive_t *interactive_ptr) -{ - wlmaker_titlebar_t *titlebar_ptr = titlebar_from_interactive( - interactive_ptr); - - const char *cursor_name_ptr = xcursor_name_default; - if (titlebar_ptr->state == TITLEBAR_MOVING) { - cursor_name_ptr = xcursor_name_move; - } - - wlr_xcursor_manager_set_cursor_image( - interactive_ptr->cursor_ptr->wlr_xcursor_manager_ptr, - cursor_name_ptr, - interactive_ptr->cursor_ptr->wlr_cursor_ptr); -} - -/* ------------------------------------------------------------------------- */ -/** - * Interactive callback: Cursor leaves the button area. - * - * @param interactive_ptr - */ -void _titlebar_leave(__UNUSED__ wlmaker_interactive_t *interactive_ptr) -{ - // Nothing to do. -} - -/* ------------------------------------------------------------------------- */ -/** - * Interactive callback: Handle cursor motion. - * - * @param interactive_ptr - * @param x - * @param y - */ -void _titlebar_motion( - wlmaker_interactive_t *interactive_ptr, - double x, double y) -{ - wlmaker_titlebar_t *titlebar_ptr = titlebar_from_interactive( - interactive_ptr); - - if (titlebar_ptr->state == TITLEBAR_CLICKED && - (fabs(titlebar_ptr->clicked_x - x) > minimal_move || - fabs(titlebar_ptr->clicked_y - y) > minimal_move)) { - titlebar_ptr->state = TITLEBAR_MOVING; - wlmaker_cursor_begin_move( - titlebar_ptr->interactive.cursor_ptr, - titlebar_ptr->view_ptr); - - wlr_xcursor_manager_set_cursor_image( - interactive_ptr->cursor_ptr->wlr_xcursor_manager_ptr, - xcursor_name_move, - interactive_ptr->cursor_ptr->wlr_cursor_ptr); - } -} - -/* ------------------------------------------------------------------------- */ -/** - * Interactive callback: Focus state changes. - * - * @param interactive_ptr - */ -static void _titlebar_focus(wlmaker_interactive_t *interactive_ptr) -{ - wlmaker_titlebar_t *titlebar_ptr = titlebar_from_interactive( - interactive_ptr); - - wlmaker_interactive_set_texture( - interactive_ptr, - interactive_ptr->focussed ? - titlebar_ptr->titlebar_buffer_ptr : - titlebar_ptr->titlebar_blurred_buffer_ptr); - if (!interactive_ptr->focussed) { - titlebar_ptr->state = TITLEBAR_IDLE; - } -} - -/* ------------------------------------------------------------------------- */ -/** - * Interactive callback: Handle cursor button, ie. button press or release. - * - * @param interactive_ptr - * @param x - * @param y - * @param wlr_pointer_button_event_ptr - */ -void _titlebar_button( - wlmaker_interactive_t *interactive_ptr, - double x, - double y, - __UNUSED__ struct wlr_pointer_button_event *wlr_pointer_button_event_ptr) -{ - wlmaker_titlebar_t *titlebar_ptr = titlebar_from_interactive( - interactive_ptr); - uint64_t now_nsec; - - if (wlr_pointer_button_event_ptr->button == BTN_RIGHT && - wlr_pointer_button_event_ptr->state == WLR_BUTTON_PRESSED) { - wlmaker_view_window_menu_show(titlebar_ptr->view_ptr); - } - - if (wlr_pointer_button_event_ptr->button != BTN_LEFT) return; - - switch (wlr_pointer_button_event_ptr->state) { - case WLR_BUTTON_PRESSED: - now_nsec = bs_mono_nsec(); - if (now_nsec - titlebar_ptr->last_click_nsec < - wlmaker_config_double_click_wait_msec * 1000000ull) { - // two clicks! will take it! - titlebar_ptr->state = TITLEBAR_IDLE; - wlmaker_view_shade(titlebar_ptr->view_ptr); - break; - } - - if (titlebar_ptr->state == TITLEBAR_IDLE) { - titlebar_ptr->state = TITLEBAR_CLICKED; - titlebar_ptr->clicked_x = x; - titlebar_ptr->clicked_y = y; - } - titlebar_ptr->last_click_nsec = now_nsec; - break; - - case WLR_BUTTON_RELEASED: - titlebar_ptr->state = TITLEBAR_IDLE; - // Reset cursor to default, if it is within our bounds. - if (wlmaker_interactive_contains(&titlebar_ptr->interactive, x, y)) { - wlr_xcursor_manager_set_cursor_image( - interactive_ptr->cursor_ptr->wlr_xcursor_manager_ptr, - xcursor_name_default, - interactive_ptr->cursor_ptr->wlr_cursor_ptr); - } - break; - - default: - /* huh, that's unexpected... */ - break; - } -} - -/* ------------------------------------------------------------------------- */ -/** - * Destroys the titlebar interactive. - * - * @param interactive_ptr - */ -void _titlebar_destroy( - wlmaker_interactive_t *interactive_ptr) -{ - wlmaker_titlebar_t *titlebar_ptr = titlebar_from_interactive( - interactive_ptr); - - if (NULL != titlebar_ptr->titlebar_buffer_ptr) { - wlr_buffer_unlock(titlebar_ptr->titlebar_buffer_ptr); - titlebar_ptr->titlebar_buffer_ptr = NULL; - } - if (NULL != titlebar_ptr->titlebar_blurred_buffer_ptr) { - wlr_buffer_unlock(titlebar_ptr->titlebar_blurred_buffer_ptr); - titlebar_ptr->titlebar_blurred_buffer_ptr = NULL; - } - free(titlebar_ptr); -} - -/* == End of titlebar.c ==================================================== */ diff --git a/src/titlebar.h b/src/titlebar.h deleted file mode 100644 index 2f44017d..00000000 --- a/src/titlebar.h +++ /dev/null @@ -1,74 +0,0 @@ -/* ========================================================================= */ -/** - * @file titlebar.h - * - * @copyright - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef __TITLEBAR_H__ -#define __TITLEBAR_H__ - -#include "cursor.h" -#include "interactive.h" - -#define WLR_USE_UNSTABLE -#include -#include -#undef WLR_USE_UNSTABLE - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * Creates a titlebar interactive. - * - * @param wlr_scene_buffer_ptr Buffer scene node to contain the button. - * @param cursor_ptr Cursor. Must outlive the titlebar. - * @param view_ptr View owning the titlebar. Must outlive titlebar. - * @param titlebar_buffer_ptr WLR buffer, title bar texture when focussed. This - * titlebar interactive will hold a consumer lock on - * it. - * @param titlebar_blurred_buffer_ptr WLR buffer, texture when blurred. This - * titlebar interactive will hold a consumer lock. - * - * @return A pointer to the interactive. Must be destroyed via - * |_titlebar_destroy|. - */ -wlmaker_interactive_t *wlmaker_titlebar_create( - struct wlr_scene_buffer *wlr_scene_buffer_ptr, - wlmaker_cursor_t *cursor_ptr, - wlmaker_view_t *view_ptr, - struct wlr_buffer *titlebar_buffer_ptr, - struct wlr_buffer *titlebar_blurred_buffer_ptr); - -/** - * Sets (replaces) the texture for the titlebar interactive. - * - * @param interactive_ptr - * @param titlebar_buffer_ptr - * @param titlebar_blurred_buffer_ptr - */ -void wlmaker_title_set_texture( - wlmaker_interactive_t *interactive_ptr, - struct wlr_buffer *titlebar_buffer_ptr, - struct wlr_buffer *titlebar_blurred_buffer_ptr); - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif /* __TITLEBAR_H__ */ -/* == End of titlebar.h ==================================================== */ diff --git a/src/toolkit/CMakeLists.txt b/src/toolkit/CMakeLists.txt index 4de4648e..aacd623b 100644 --- a/src/toolkit/CMakeLists.txt +++ b/src/toolkit/CMakeLists.txt @@ -17,12 +17,53 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.13) SET(PUBLIC_HEADER_FILES gfxbuf.h primitives.h - style.h) + style.h + toolkit.h + util.h + + bordered.h + box.h + buffer.h + button.h + container.h + content.h + element.h + env.h + fsm.h + input.h + surface.h + rectangle.h + resizebar.h + resizebar_area.h + titlebar.h + titlebar_button.h + titlebar_title.h + window.h + workspace.h) ADD_LIBRARY(toolkit STATIC) TARGET_SOURCES(toolkit PRIVATE + bordered.c + box.c + buffer.c + button.c + container.c + content.c + element.c + env.c + fsm.c gfxbuf.c - primitives.c) + primitives.c + surface.c + rectangle.c + resizebar.c + resizebar_area.c + titlebar.c + titlebar_button.c + titlebar_title.c + util.c + window.c + workspace.c) TARGET_INCLUDE_DIRECTORIES( toolkit PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/.. @@ -49,6 +90,7 @@ TARGET_LINK_LIBRARIES( base PkgConfig::CAIRO PkgConfig::LIBDRM + PkgConfig::WAYLAND PkgConfig::WLROOTS ) diff --git a/src/toolkit/bordered.c b/src/toolkit/bordered.c new file mode 100644 index 00000000..113957cc --- /dev/null +++ b/src/toolkit/bordered.c @@ -0,0 +1,313 @@ +/* ========================================================================= */ +/** + * @file bordered.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bordered.h" + +/* == Declarations ========================================================= */ + +static void _wlmtk_bordered_container_update_layout( + wlmtk_container_t *container_ptr); + +static wlmtk_rectangle_t * _wlmtk_bordered_create_border_rectangle( + wlmtk_bordered_t *bordered_ptr, + wlmtk_env_t *env_ptr); +static void _wlmtk_bordered_destroy_border_rectangle( + wlmtk_bordered_t *bordered_ptr, + wlmtk_rectangle_t **rectangle_ptr_ptr); +static void _wlmtk_bordered_set_positions(wlmtk_bordered_t *bordered_ptr); + +/* == Data ================================================================= */ + +/** Virtual method table: @ref wlmtk_container_t at @ref wlmtk_bordered_t. */ +static const wlmtk_container_vmt_t bordered_container_vmt = { + .update_layout = _wlmtk_bordered_container_update_layout, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +bool wlmtk_bordered_init(wlmtk_bordered_t *bordered_ptr, + wlmtk_env_t *env_ptr, + wlmtk_element_t *element_ptr, + const wlmtk_margin_style_t *style_ptr) +{ + BS_ASSERT(NULL != bordered_ptr); + memset(bordered_ptr, 0, sizeof(wlmtk_bordered_t)); + if (!wlmtk_container_init(&bordered_ptr->super_container, env_ptr)) { + return false; + } + bordered_ptr->orig_super_container_vmt = wlmtk_container_extend( + &bordered_ptr->super_container, &bordered_container_vmt); + memcpy(&bordered_ptr->style, style_ptr, sizeof(wlmtk_margin_style_t)); + + bordered_ptr->element_ptr = element_ptr; + wlmtk_container_add_element(&bordered_ptr->super_container, + bordered_ptr->element_ptr); + + bordered_ptr->northern_border_rectangle_ptr = + _wlmtk_bordered_create_border_rectangle(bordered_ptr, env_ptr); + bordered_ptr->eastern_border_rectangle_ptr = + _wlmtk_bordered_create_border_rectangle(bordered_ptr, env_ptr); + bordered_ptr->southern_border_rectangle_ptr = + _wlmtk_bordered_create_border_rectangle(bordered_ptr, env_ptr); + bordered_ptr->western_border_rectangle_ptr = + _wlmtk_bordered_create_border_rectangle(bordered_ptr, env_ptr); + if (NULL == bordered_ptr->northern_border_rectangle_ptr || + NULL == bordered_ptr->eastern_border_rectangle_ptr || + NULL == bordered_ptr->southern_border_rectangle_ptr || + NULL == bordered_ptr->western_border_rectangle_ptr) { + wlmtk_bordered_fini(bordered_ptr); + return false; + } + + _wlmtk_bordered_set_positions(bordered_ptr); + return true; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_bordered_fini(wlmtk_bordered_t *bordered_ptr) +{ + _wlmtk_bordered_destroy_border_rectangle( + bordered_ptr, &bordered_ptr->western_border_rectangle_ptr); + _wlmtk_bordered_destroy_border_rectangle( + bordered_ptr, &bordered_ptr->southern_border_rectangle_ptr); + _wlmtk_bordered_destroy_border_rectangle( + bordered_ptr, &bordered_ptr->eastern_border_rectangle_ptr); + _wlmtk_bordered_destroy_border_rectangle( + bordered_ptr, &bordered_ptr->northern_border_rectangle_ptr); + + wlmtk_container_remove_element(&bordered_ptr->super_container, + bordered_ptr->element_ptr); + wlmtk_container_fini(&bordered_ptr->super_container); + memset(bordered_ptr, 0, sizeof(wlmtk_bordered_t)); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_bordered_set_style(wlmtk_bordered_t *bordered_ptr, + const wlmtk_margin_style_t *style_ptr) +{ + memcpy(&bordered_ptr->style, style_ptr, sizeof(wlmtk_margin_style_t)); + + _wlmtk_bordered_container_update_layout(&bordered_ptr->super_container); + + // Guard clause. Actually, if *any* of the rectangles was not created. + if (NULL == bordered_ptr->western_border_rectangle_ptr) return; + + wlmtk_rectangle_set_color( + bordered_ptr->northern_border_rectangle_ptr, style_ptr->color); + wlmtk_rectangle_set_color( + bordered_ptr->eastern_border_rectangle_ptr, style_ptr->color); + wlmtk_rectangle_set_color( + bordered_ptr->southern_border_rectangle_ptr, style_ptr->color); + wlmtk_rectangle_set_color( + bordered_ptr->western_border_rectangle_ptr, style_ptr->color); +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** + * Updates the layout of the bordered element. + * + * @param container_ptr + */ +void _wlmtk_bordered_container_update_layout( + wlmtk_container_t *container_ptr) +{ + wlmtk_bordered_t *bordered_ptr = BS_CONTAINER_OF( + container_ptr, wlmtk_bordered_t, super_container); + + _wlmtk_bordered_set_positions(bordered_ptr); + + bordered_ptr->orig_super_container_vmt.update_layout(container_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Creates a border rectangle and adds it to `bordered_ptr`. */ +wlmtk_rectangle_t * _wlmtk_bordered_create_border_rectangle( + wlmtk_bordered_t *bordered_ptr, + wlmtk_env_t *env_ptr) +{ + wlmtk_rectangle_t *rectangle_ptr = wlmtk_rectangle_create( + env_ptr, 0, 0, bordered_ptr->style.color); + if (NULL == rectangle_ptr) return NULL; + + wlmtk_element_set_visible(wlmtk_rectangle_element(rectangle_ptr), true); + wlmtk_container_add_element_atop( + &bordered_ptr->super_container, + NULL, + wlmtk_rectangle_element(rectangle_ptr)); + + return rectangle_ptr; +} + +/* ------------------------------------------------------------------------- */ +/** Removes the rectangle from `bordered_ptr`, destroys it and NULLs it. */ +void _wlmtk_bordered_destroy_border_rectangle( + wlmtk_bordered_t *bordered_ptr, + wlmtk_rectangle_t **rectangle_ptr_ptr) +{ + if (NULL == *rectangle_ptr_ptr) return; + + wlmtk_container_remove_element( + &bordered_ptr->super_container, + wlmtk_rectangle_element(*rectangle_ptr_ptr)); + wlmtk_rectangle_destroy(*rectangle_ptr_ptr); + *rectangle_ptr_ptr = NULL; +} + +/* ------------------------------------------------------------------------- */ +/** + * Updates the position of all 4 border elements. + * + * Retrieves the position and dimensions of @ref wlmtk_bordered_t::element_ptr + * and arranges the 4 border elements around it. + * + * @param bordered_ptr + */ +void _wlmtk_bordered_set_positions(wlmtk_bordered_t *bordered_ptr) +{ + int x1, y1, x2, y2; + int x_pos, y_pos; + + if (NULL == bordered_ptr->western_border_rectangle_ptr) return; + + int margin = bordered_ptr->style.width; + + wlmtk_element_get_dimensions( + bordered_ptr->element_ptr, &x1, &y1, &x2, &y2); + x_pos = -x1 + margin; + y_pos = -y1 + margin; + int width = x2 - x1; + int height = y2 - y1; + wlmtk_element_set_position(bordered_ptr->element_ptr, x_pos, y_pos); + + wlmtk_element_set_position( + wlmtk_rectangle_element(bordered_ptr->northern_border_rectangle_ptr), + x_pos - margin, y_pos - margin); + wlmtk_rectangle_set_size( + bordered_ptr->northern_border_rectangle_ptr, + width + 2 * margin, margin); + + wlmtk_element_set_position( + wlmtk_rectangle_element(bordered_ptr->eastern_border_rectangle_ptr), + x_pos + width, y_pos); + wlmtk_rectangle_set_size( + bordered_ptr->eastern_border_rectangle_ptr, + margin, height); + + wlmtk_element_set_position( + wlmtk_rectangle_element(bordered_ptr->southern_border_rectangle_ptr), + x_pos - margin, y_pos + height); + wlmtk_rectangle_set_size( + bordered_ptr->southern_border_rectangle_ptr, + width + 2 * margin, margin); + + wlmtk_element_set_position( + wlmtk_rectangle_element(bordered_ptr->western_border_rectangle_ptr), + x_pos - margin, y_pos); + wlmtk_rectangle_set_size( + bordered_ptr->western_border_rectangle_ptr, + margin, height); +} + +/* == Unit tests =========================================================== */ + +static void test_init_fini(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_bordered_test_cases[] = { + { 1, "init_fini", test_init_fini }, + { 0, NULL, NULL } +}; + +/** Style used for tests. */ +static const wlmtk_margin_style_t test_style = { + .width = 2, + .color = 0xff000000 +}; + +/** Helper: Tests that the rectangle is positioned as specified. */ +void test_rectangle_pos(bs_test_t *test_ptr, wlmtk_rectangle_t *rect_ptr, + int x, int y, int width, int height) +{ + wlmtk_element_t *elem_ptr = wlmtk_rectangle_element(rect_ptr); + BS_TEST_VERIFY_EQ(test_ptr, x, elem_ptr->x); + BS_TEST_VERIFY_EQ(test_ptr, y, elem_ptr->y); + + int x1, y1, x2, y2; + wlmtk_element_get_dimensions(elem_ptr, &x1, &y1, &x2, &y2); + BS_TEST_VERIFY_EQ(test_ptr, 0, x1); + BS_TEST_VERIFY_EQ(test_ptr, 0, y1); + BS_TEST_VERIFY_EQ(test_ptr, width, x2 - x1); + BS_TEST_VERIFY_EQ(test_ptr, height, y2 - y1); +} + + +/* ------------------------------------------------------------------------- */ +/** Exercises setup and teardown. */ +void test_init_fini(bs_test_t *test_ptr) +{ + wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); + fe_ptr->dimensions.width = 100; + fe_ptr->dimensions.height = 20; + wlmtk_element_set_position(&fe_ptr->element, -10, -4); + + wlmtk_bordered_t bordered; + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_bordered_init( + &bordered, NULL, &fe_ptr->element, &test_style)); + + // Positions of border elements. + test_rectangle_pos( + test_ptr, bordered.northern_border_rectangle_ptr, + 0, 0, 104, 2); + test_rectangle_pos( + test_ptr, bordered.eastern_border_rectangle_ptr, + 102, 2, 2, 20); + test_rectangle_pos( + test_ptr, bordered.southern_border_rectangle_ptr, + 0, 22, 104, 2); + test_rectangle_pos( + test_ptr, bordered.western_border_rectangle_ptr, + 0, 2, 2, 20); + + // Update layout, test updated positions. + fe_ptr->dimensions.width = 200; + fe_ptr->dimensions.height = 120; + wlmtk_container_update_layout(&bordered.super_container); + test_rectangle_pos( + test_ptr, bordered.northern_border_rectangle_ptr, + 0, 0, 204, 2); + test_rectangle_pos( + test_ptr, bordered.eastern_border_rectangle_ptr, + 202, 2, 2, 120); + test_rectangle_pos( + test_ptr, bordered.southern_border_rectangle_ptr, + 0, 122, 204, 2); + test_rectangle_pos( + test_ptr, bordered.western_border_rectangle_ptr, + 0, 2, 2, 120); + + wlmtk_bordered_fini(&bordered); + + wlmtk_element_destroy(&fe_ptr->element); +} + + +/* == End of bordered.c ==================================================== */ diff --git a/src/toolkit/bordered.h b/src/toolkit/bordered.h new file mode 100644 index 00000000..797d9249 --- /dev/null +++ b/src/toolkit/bordered.h @@ -0,0 +1,98 @@ +/* ========================================================================= */ +/** + * @file bordered.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_BORDERED_H__ +#define __WLMTK_BORDERED_H__ + +#include "container.h" +#include "rectangle.h" +#include "style.h" + +/** Forward declaration: Bordered container state. */ +typedef struct _wlmtk_bordered_t wlmtk_bordered_t; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** State of the bordered container. */ +struct _wlmtk_bordered_t { + /** Super class of the bordered. */ + wlmtk_container_t super_container; + /** Virtual method table of the super container before extending it. */ + wlmtk_container_vmt_t orig_super_container_vmt; + + /** Points to the element that will be enclosed by the border. */ + wlmtk_element_t *element_ptr; + /** Style of the border. */ + wlmtk_margin_style_t style; + + /** Border element at the northern side. Includes east + west corners. */ + wlmtk_rectangle_t *northern_border_rectangle_ptr; + /** Border element at the eastern side. */ + wlmtk_rectangle_t *eastern_border_rectangle_ptr; + /** Border element at the southern side. Includes east + west corners. */ + wlmtk_rectangle_t *southern_border_rectangle_ptr; + /** Border element at the western side. */ + wlmtk_rectangle_t *western_border_rectangle_ptr; +}; + +/** + * Initializes the bordered element. + * + * The bordered element positions the element within such that north-western + * corner is at (0, 0). + * + * @param bordered_ptr + * @param env_ptr + * @param element_ptr + * @param style_ptr + * + * @return true on success. + */ +bool wlmtk_bordered_init(wlmtk_bordered_t *bordered_ptr, + wlmtk_env_t *env_ptr, + wlmtk_element_t *element_ptr, + const wlmtk_margin_style_t *style_ptr); + +/** + * Un-initializes the bordered element. + * + * @param bordered_ptr + */ +void wlmtk_bordered_fini(wlmtk_bordered_t *bordered_ptr); + +/** + * Updates the style. + * + * @param bordered_ptr + * @param style_ptr + */ +void wlmtk_bordered_set_style(wlmtk_bordered_t *bordered_ptr, + const wlmtk_margin_style_t *style_ptr); + +/** Unit test cases. */ +extern const bs_test_case_t wlmtk_bordered_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_BORDERED_H__ */ +/* == End of bordered.h ==================================================== */ diff --git a/src/toolkit/box.c b/src/toolkit/box.c new file mode 100644 index 00000000..3dfcc30a --- /dev/null +++ b/src/toolkit/box.c @@ -0,0 +1,367 @@ +/* ========================================================================= */ +/** + * @file box.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "box.h" + +#include "rectangle.h" + +/* == Declarations ========================================================= */ + +static void _wlmtk_box_container_update_layout( + wlmtk_container_t *container_ptr); +static bs_dllist_node_t *create_margin(wlmtk_box_t *box_ptr); + +/* == Data ================================================================= */ + +/** Virtual method table: @ref wlmtk_container_t at @ref wlmtk_box_t level. */ +static const wlmtk_container_vmt_t box_container_vmt = { + .update_layout = _wlmtk_box_container_update_layout, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +bool wlmtk_box_init( + wlmtk_box_t *box_ptr, + wlmtk_env_t *env_ptr, + wlmtk_box_orientation_t orientation, + const wlmtk_margin_style_t *style_ptr) +{ + BS_ASSERT(NULL != box_ptr); + memset(box_ptr, 0, sizeof(wlmtk_box_t)); + if (!wlmtk_container_init(&box_ptr->super_container, env_ptr)) { + return false; + } + box_ptr->orig_super_container_vmt = wlmtk_container_extend( + &box_ptr->super_container, &box_container_vmt); + box_ptr->env_ptr = env_ptr; + memcpy(&box_ptr->style, style_ptr, sizeof(wlmtk_margin_style_t)); + + if (!wlmtk_container_init(&box_ptr->element_container, env_ptr)) { + wlmtk_box_fini(box_ptr); + return false; + } + wlmtk_element_set_visible(&box_ptr->element_container.super_element, true); + wlmtk_container_add_element(&box_ptr->super_container, + &box_ptr->element_container.super_element); + if (!wlmtk_container_init(&box_ptr->margin_container, env_ptr)) { + wlmtk_box_fini(box_ptr); + return false; + } + wlmtk_element_set_visible(&box_ptr->margin_container.super_element, true); + // Keep margins behind the box's elements. + wlmtk_container_add_element_atop( + &box_ptr->super_container, + NULL, + &box_ptr->margin_container.super_element); + + box_ptr->orientation = orientation; + return true; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_box_fini(wlmtk_box_t *box_ptr) +{ + if (NULL != box_ptr->margin_container.super_element.parent_container_ptr) { + wlmtk_container_remove_element( + &box_ptr->super_container, + &box_ptr->margin_container.super_element); + } + if (NULL != box_ptr->element_container.super_element.parent_container_ptr) { + wlmtk_container_remove_element( + &box_ptr->super_container, + &box_ptr->element_container.super_element); + } + + wlmtk_container_fini(&box_ptr->super_container); + memset(box_ptr, 0, sizeof(wlmtk_box_t)); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_box_add_element_front( + wlmtk_box_t *box_ptr, + wlmtk_element_t *element_ptr) +{ + wlmtk_container_add_element(&box_ptr->element_container, element_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_box_add_element_back( + wlmtk_box_t *box_ptr, + wlmtk_element_t *element_ptr) +{ + wlmtk_container_add_element_atop( + &box_ptr->element_container, NULL, element_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_box_remove_element(wlmtk_box_t *box_ptr, wlmtk_element_t *element_ptr) +{ + wlmtk_container_remove_element(&box_ptr->element_container, element_ptr); +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** + * Updates the layout of the box. + * + * Steps through all visible elements, and sets their position to be + * left-to-right. Also updates and repositions all margin elements. + * + * @param container_ptr + */ +void _wlmtk_box_container_update_layout( + wlmtk_container_t *container_ptr) +{ + wlmtk_box_t *box_ptr = BS_CONTAINER_OF( + container_ptr, wlmtk_box_t, super_container); + wlmtk_element_t *margin_element_ptr = NULL; + + int margin_x = 0; + int margin_y = 0; + int margin_width = box_ptr->style.width; + int margin_height = box_ptr->style.width; + + size_t visible_elements = 0; + for (bs_dllist_node_t *dlnode_ptr = box_ptr->element_container.elements.head_ptr; + dlnode_ptr != NULL; + dlnode_ptr = dlnode_ptr->next_ptr) { + wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); + if (element_ptr->visible) visible_elements++; + } + + int position = 0; + bs_dllist_node_t *margin_dlnode_ptr = box_ptr->margin_container.elements.head_ptr; + for (bs_dllist_node_t *dlnode_ptr = box_ptr->element_container.elements.head_ptr; + dlnode_ptr != NULL; + dlnode_ptr = dlnode_ptr->next_ptr) { + wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); + if (!element_ptr->visible) continue; + + int left, top, right, bottom; + wlmtk_element_get_dimensions(element_ptr, &left, &top, &right, &bottom); + int x, y; + wlmtk_element_get_position(element_ptr, &x, &y); + + switch (box_ptr->orientation) { + case WLMTK_BOX_HORIZONTAL: + x = position - left; + margin_x = position + right - left; + margin_height = bottom - top; + position = margin_x + box_ptr->style.width; + break; + + case WLMTK_BOX_VERTICAL: + y = position - top; + margin_y = position + bottom - top; + margin_width = right - left; + position = margin_y + box_ptr->style.width; + break; + + default: + bs_log(BS_FATAL, "Weird orientation %d.", box_ptr->orientation); + } + wlmtk_element_set_position(element_ptr, x, y); + visible_elements--; + + // Early exit: No margin needed, if there's no next element. + if (NULL == dlnode_ptr->next_ptr || 0 >= visible_elements) break; + + // If required: Create new margin, then position the margin element. + if (NULL == margin_dlnode_ptr) { + margin_dlnode_ptr = create_margin(box_ptr); + } + margin_element_ptr = wlmtk_element_from_dlnode(margin_dlnode_ptr); + wlmtk_element_set_position(margin_element_ptr, margin_x, margin_y); + wlmtk_rectangle_set_size( + wlmtk_rectangle_from_element(margin_element_ptr), + margin_width, margin_height); + + margin_dlnode_ptr = margin_dlnode_ptr->next_ptr; + } + + // Remove excess margin nodes. + while (NULL != margin_dlnode_ptr) { + margin_element_ptr = wlmtk_element_from_dlnode(margin_dlnode_ptr); + margin_dlnode_ptr = margin_dlnode_ptr->next_ptr; + wlmtk_container_remove_element( + &box_ptr->margin_container, margin_element_ptr); + wlmtk_element_destroy(margin_element_ptr); + } + + // Run the base class' update layout; may update pointer focus. + // We do this only after having updated the position of the elements. + box_ptr->orig_super_container_vmt.update_layout(container_ptr); + + // configure parent container. + if (NULL != container_ptr->super_element.parent_container_ptr) { + wlmtk_container_update_layout( + container_ptr->super_element.parent_container_ptr); + } +} + +/* ------------------------------------------------------------------------- */ +/** Creates a new margin element, and returns the dlnode. */ +bs_dllist_node_t *create_margin(wlmtk_box_t *box_ptr) +{ + wlmtk_rectangle_t *rect_ptr = wlmtk_rectangle_create( + box_ptr->env_ptr, 0, 0, box_ptr->style.color); + BS_ASSERT(NULL != rect_ptr); + + wlmtk_container_add_element_atop( + &box_ptr->margin_container, + NULL, + wlmtk_rectangle_element(rect_ptr)); + wlmtk_element_set_visible( + wlmtk_rectangle_element(rect_ptr), true); + + return wlmtk_dlnode_from_element(wlmtk_rectangle_element(rect_ptr)); +} + +/* == Unit tests =========================================================== */ + +static void test_init_fini(bs_test_t *test_ptr); +static void test_layout_horizontal(bs_test_t *test_ptr); +static void test_layout_vertical(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_box_test_cases[] = { + { 1, "init_fini", test_init_fini }, + { 1, "layout_horizontal", test_layout_horizontal }, + { 1, "layout_vertical", test_layout_vertical }, + { 0, NULL, NULL } +}; + +/** Style used for tests. */ +static const wlmtk_margin_style_t test_style = { + .width = 2, + .color = 0xff000000 +}; + +/* ------------------------------------------------------------------------- */ +/** Exercises setup and teardown. */ +void test_init_fini(bs_test_t *test_ptr) +{ + wlmtk_box_t box; + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_box_init( + &box, NULL, WLMTK_BOX_HORIZONTAL, &test_style)); + wlmtk_box_fini(&box); +} + +/* ------------------------------------------------------------------------- */ +/** Tests layouting horizontally */ +void test_layout_horizontal(bs_test_t *test_ptr) +{ + wlmtk_box_t box; + wlmtk_box_init(&box, NULL, WLMTK_BOX_HORIZONTAL, &test_style); + + wlmtk_fake_element_t *e1_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&e1_ptr->element, true); + e1_ptr->dimensions.width = 10; + e1_ptr->dimensions.height = 1; + wlmtk_fake_element_t *e2_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&e2_ptr->element, false); + e2_ptr->dimensions.width = 20; + e1_ptr->dimensions.height = 2; + wlmtk_fake_element_t *e3_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&e3_ptr->element, true); + e3_ptr->dimensions.width = 40; + e3_ptr->dimensions.height = 4; + + // Note: Elements are added "in front" == left. + wlmtk_box_add_element_front(&box, &e1_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&box.margin_container.elements)); + wlmtk_box_add_element_front(&box, &e2_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&box.margin_container.elements)); + wlmtk_box_add_element_front(&box, &e3_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, 1, bs_dllist_size(&box.margin_container.elements)); + + // Layout: e3 | e1 (e2 is invisible). + BS_TEST_VERIFY_EQ(test_ptr, 42, e1_ptr->element.x); + BS_TEST_VERIFY_EQ(test_ptr, 0, e1_ptr->element.y); + BS_TEST_VERIFY_EQ(test_ptr, 0, e2_ptr->element.x); + BS_TEST_VERIFY_EQ(test_ptr, 0, e2_ptr->element.y); + BS_TEST_VERIFY_EQ(test_ptr, 0, e3_ptr->element.x); + BS_TEST_VERIFY_EQ(test_ptr, 0, e3_ptr->element.y); + + // Make e2 visible, now we should have: e3 | e2 | e1. + wlmtk_element_set_visible(&e2_ptr->element, true); + BS_TEST_VERIFY_EQ(test_ptr, 64, e1_ptr->element.x); + BS_TEST_VERIFY_EQ(test_ptr, 42, e2_ptr->element.x); + BS_TEST_VERIFY_EQ(test_ptr, 0, e3_ptr->element.x); + BS_TEST_VERIFY_EQ(test_ptr, 2, bs_dllist_size(&box.margin_container.elements)); + + wlmtk_element_set_visible(&e1_ptr->element, false); + BS_TEST_VERIFY_EQ(test_ptr, 1, bs_dllist_size(&box.margin_container.elements)); + wlmtk_element_set_visible(&e1_ptr->element, true); + + // Remove elements. Must update each. + wlmtk_box_remove_element(&box, &e3_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, 22, e1_ptr->element.x); + BS_TEST_VERIFY_EQ(test_ptr, 0, e2_ptr->element.x); + BS_TEST_VERIFY_EQ(test_ptr, 1, bs_dllist_size(&box.margin_container.elements)); + wlmtk_box_remove_element(&box, &e2_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, 0, e1_ptr->element.x); + BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&box.margin_container.elements)); + wlmtk_box_remove_element(&box, &e1_ptr->element); + + wlmtk_element_destroy(&e3_ptr->element); + wlmtk_element_destroy(&e2_ptr->element); + wlmtk_element_destroy(&e1_ptr->element); + wlmtk_box_fini(&box); +} + +/* ------------------------------------------------------------------------- */ +/** Tests layouting vertically */ +void test_layout_vertical(bs_test_t *test_ptr) +{ + wlmtk_box_t box; + wlmtk_box_init(&box, NULL, WLMTK_BOX_VERTICAL, &test_style); + + wlmtk_fake_element_t *e1_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&e1_ptr->element, true); + e1_ptr->dimensions.width = 100; + e1_ptr->dimensions.height = 10; + wlmtk_fake_element_t *e2_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&e2_ptr->element, true); + e2_ptr->dimensions.width = 200; + e2_ptr->dimensions.height = 20; + + // Note: Elements are added "in front" == left. + wlmtk_box_add_element_front(&box, &e1_ptr->element); + wlmtk_box_add_element_front(&box, &e2_ptr->element); + + // Layout: e2 | e1. + BS_TEST_VERIFY_EQ(test_ptr, 0, e1_ptr->element.x); + BS_TEST_VERIFY_EQ(test_ptr, 22, e1_ptr->element.y); + BS_TEST_VERIFY_EQ(test_ptr, 0, e2_ptr->element.x); + BS_TEST_VERIFY_EQ(test_ptr, 0, e2_ptr->element.y); + + // Remove elements. Must update each. + wlmtk_box_remove_element(&box, &e2_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, 0, e1_ptr->element.y); + wlmtk_box_remove_element(&box, &e1_ptr->element); + + wlmtk_element_destroy(&e2_ptr->element); + wlmtk_element_destroy(&e1_ptr->element); + wlmtk_box_fini(&box); +} + +/* == End of box.c ========================================================= */ diff --git a/src/toolkit/box.h b/src/toolkit/box.h new file mode 100644 index 00000000..79b421ed --- /dev/null +++ b/src/toolkit/box.h @@ -0,0 +1,119 @@ +/* ========================================================================= */ +/** + * @file box.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_BOX_H__ +#define __WLMTK_BOX_H__ + +/** Forward declaration: Box. */ +typedef struct _wlmtk_box_t wlmtk_box_t; + +#include "container.h" +#include "style.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Orientation of the box. */ +typedef enum { + /** Horizontal box layout. The container's "front" is on the left. */ + WLMTK_BOX_HORIZONTAL, + /** Vertical box layout. The container's "front" is the top. */ + WLMTK_BOX_VERTICAL, +} wlmtk_box_orientation_t; + +/** State of the box. */ +struct _wlmtk_box_t { + /** Super class of the box. */ + wlmtk_container_t super_container; + /** Virtual method table of the superclass' container. */ + wlmtk_container_vmt_t orig_super_container_vmt; + /** Orientation of the box. */ + wlmtk_box_orientation_t orientation; + + /** Environment. */ + wlmtk_env_t *env_ptr; + + /** Container for the box's elements. */ + wlmtk_container_t element_container; + /** Container for margin elements. */ + wlmtk_container_t margin_container; + + /** Margin style. */ + wlmtk_margin_style_t style; +}; + +/** + * Initializes the box with the provided virtual method table. + * + * @param box_ptr + * @param orientation + * @param env_ptr + * @param style_ptr + * + * @return true on success. + */ +bool wlmtk_box_init( + wlmtk_box_t *box_ptr, + wlmtk_env_t *env_ptr, + wlmtk_box_orientation_t orientation, + const wlmtk_margin_style_t *style_ptr); + +/** + * Un-initializes the box. + * + * @param box_ptr + */ +void wlmtk_box_fini(wlmtk_box_t *box_ptr); + +/** + * Adds `element_ptr` to the front of the box. + * + * @param box_ptr + * @param element_ptr + */ +void wlmtk_box_add_element_front(wlmtk_box_t *box_ptr, wlmtk_element_t *element_ptr); + +/** + * Adds `element_ptr` to the back of the box. + * + * @param box_ptr + * @param element_ptr + */ +void wlmtk_box_add_element_back(wlmtk_box_t *box_ptr, wlmtk_element_t *element_ptr); + +/** + * Removes `element_ptr` from the box. + * + * Requires that element_ptr is an element of the box. + * + * @param box_ptr + * @param element_ptr + */ +void wlmtk_box_remove_element(wlmtk_box_t *box_ptr, wlmtk_element_t *element_ptr); + +/** Unit tests. */ +extern const bs_test_case_t wlmtk_box_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_BOX_H__ */ +/* == End of box.h ========================================================= */ diff --git a/src/toolkit/buffer.c b/src/toolkit/buffer.c new file mode 100644 index 00000000..351e0fe7 --- /dev/null +++ b/src/toolkit/buffer.c @@ -0,0 +1,192 @@ +/* ========================================================================= */ +/** + * @file buffer.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "buffer.h" + +#include "util.h" + +#define WLR_USE_UNSTABLE +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +static struct wlr_scene_node *element_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr); +static void element_get_dimensions( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr); +static void handle_wlr_scene_buffer_node_destroy( + struct wl_listener *listener_ptr, + void *data_ptr); + +/* == Data ================================================================= */ + +/** Method table for the buffer's virtual methods. */ +static const wlmtk_element_vmt_t buffer_element_vmt = { + .create_scene_node = element_create_scene_node, + .get_dimensions = element_get_dimensions, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +bool wlmtk_buffer_init(wlmtk_buffer_t *buffer_ptr, + wlmtk_env_t *env_ptr) +{ + BS_ASSERT(NULL != buffer_ptr); + memset(buffer_ptr, 0, sizeof(wlmtk_buffer_t)); + + if (!wlmtk_element_init(&buffer_ptr->super_element, env_ptr)) { + return false; + } + buffer_ptr->orig_super_element_vmt = wlmtk_element_extend( + &buffer_ptr->super_element, &buffer_element_vmt); + return true; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_buffer_fini(wlmtk_buffer_t *buffer_ptr) +{ + if (NULL != buffer_ptr->wlr_buffer_ptr) { + wlr_buffer_unlock(buffer_ptr->wlr_buffer_ptr); + buffer_ptr->wlr_buffer_ptr = NULL; + } + + if (NULL != buffer_ptr->wlr_scene_buffer_ptr) { + wlr_scene_node_destroy(&buffer_ptr->wlr_scene_buffer_ptr->node); + buffer_ptr->wlr_scene_buffer_ptr = NULL; + } + + wlmtk_element_fini(&buffer_ptr->super_element); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_buffer_set( + wlmtk_buffer_t *buffer_ptr, + struct wlr_buffer *wlr_buffer_ptr) +{ + if (NULL != buffer_ptr->wlr_buffer_ptr) { + wlr_buffer_unlock(buffer_ptr->wlr_buffer_ptr); + } + + if (NULL != wlr_buffer_ptr) { + buffer_ptr->wlr_buffer_ptr = wlr_buffer_lock(wlr_buffer_ptr); + } else { + buffer_ptr->wlr_buffer_ptr = NULL; + } + + if (NULL != buffer_ptr->wlr_scene_buffer_ptr) { + wlr_scene_buffer_set_buffer( + buffer_ptr->wlr_scene_buffer_ptr, + buffer_ptr->wlr_buffer_ptr); + } +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** + * Implementation of the superclass wlmtk_element_t::create_scene_node method. + * + * Creates a `struct wlr_scene_buffer` attached to `wlr_scene_tree_ptr`. + * + * @param element_ptr + * @param wlr_scene_tree_ptr + */ +struct wlr_scene_node *element_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr) +{ + wlmtk_buffer_t *buffer_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_buffer_t, super_element); + + BS_ASSERT(NULL == buffer_ptr->wlr_scene_buffer_ptr); + buffer_ptr->wlr_scene_buffer_ptr = wlr_scene_buffer_create( + wlr_scene_tree_ptr, + buffer_ptr->wlr_buffer_ptr); + BS_ASSERT(NULL != buffer_ptr->wlr_scene_buffer_ptr); + + wlmtk_util_connect_listener_signal( + &buffer_ptr->wlr_scene_buffer_ptr->node.events.destroy, + &buffer_ptr->wlr_scene_buffer_node_destroy_listener, + handle_wlr_scene_buffer_node_destroy); + return &buffer_ptr->wlr_scene_buffer_ptr->node; +} + +/* ------------------------------------------------------------------------- */ +/** + * Implementation of the element's get_dimensions method: Return dimensions. + * + * @param element_ptr + * @param left_ptr Leftmost position. May be NULL. + * @param top_ptr Topmost position. May be NULL. + * @param right_ptr Rightmost position. Ma be NULL. + * @param bottom_ptr Bottommost position. May be NULL. + */ +void element_get_dimensions( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr) +{ + wlmtk_buffer_t *buffer_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_buffer_t, super_element); + + if (NULL != left_ptr) *left_ptr = 0; + if (NULL != top_ptr) *top_ptr = 0; + + if (NULL == buffer_ptr->wlr_buffer_ptr) { + if (NULL != right_ptr) *right_ptr = 0; + if (NULL != bottom_ptr) *bottom_ptr = 0; + return; + } + + if (NULL != right_ptr) *right_ptr = buffer_ptr->wlr_buffer_ptr->width; + if (NULL != bottom_ptr) *bottom_ptr = buffer_ptr->wlr_buffer_ptr->height; +} + +/* ------------------------------------------------------------------------- */ +/** + * Handles the 'destroy' callback of wlr_scene_buffer_ptr->node. + * + * Will reset the wlr_scene_buffer_ptr value. Destruction of the node had + * been triggered (hence the callback). + * + * @param listener_ptr + * @param data_ptr + */ +void handle_wlr_scene_buffer_node_destroy( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_buffer_t *buffer_ptr = BS_CONTAINER_OF( + listener_ptr, wlmtk_buffer_t, wlr_scene_buffer_node_destroy_listener); + + buffer_ptr->wlr_scene_buffer_ptr = NULL; + wl_list_remove(&buffer_ptr->wlr_scene_buffer_node_destroy_listener.link); +} + +/* == End of buffer.c ====================================================== */ diff --git a/src/toolkit/buffer.h b/src/toolkit/buffer.h new file mode 100644 index 00000000..87655b9c --- /dev/null +++ b/src/toolkit/buffer.h @@ -0,0 +1,90 @@ +/* ========================================================================= */ +/** + * @file buffer.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_BUFFER_H__ +#define __WLMTK_BUFFER_H__ + +#include + +/** Forward declaration: Buffer state. */ +typedef struct _wlmtk_buffer_t wlmtk_buffer_t; + +#include "element.h" + +/** Forward declaration. */ +struct wlr_buffer; +/** Forward declaration. */ +struct wlr_scene_buffer; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** State of a texture-backed buffer. */ +struct _wlmtk_buffer_t { + /** Super class of the buffer: An element. */ + wlmtk_element_t super_element; + /** Virtual method table of the super element before extending it. */ + wlmtk_element_vmt_t orig_super_element_vmt; + + /** WLR buffer holding the contents. */ + struct wlr_buffer *wlr_buffer_ptr; + /** Scene graph API node. Only set after calling `create_scene_node`. */ + struct wlr_scene_buffer *wlr_scene_buffer_ptr; + + /** Listener for the `destroy` signal of `wlr_scene_buffer_ptr->node`. */ + struct wl_listener wlr_scene_buffer_node_destroy_listener; +}; + +/** + * Initializes the buffer. + * + * @param buffer_ptr + * @param env_ptr + * + * @return true on success. + */ +bool wlmtk_buffer_init(wlmtk_buffer_t *buffer_ptr, + wlmtk_env_t *env_ptr); + +/** + * Cleans up the buffer. + * + * @param buffer_ptr + */ +void wlmtk_buffer_fini(wlmtk_buffer_t *buffer_ptr); + +/** + * Sets (or updates) buffer contents. + * + * @param buffer_ptr + * @param wlr_buffer_ptr A WLR buffer to use for the update. That buffer + * will get locked by @ref wlmtk_buffer_t for the + * duration of it's use. + */ +void wlmtk_buffer_set( + wlmtk_buffer_t *buffer_ptr, + struct wlr_buffer *wlr_buffer_ptr); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_BUFFER_H__ */ +/* == End of buffer.h ====================================================== */ diff --git a/src/toolkit/button.c b/src/toolkit/button.c new file mode 100644 index 00000000..c8a59e28 --- /dev/null +++ b/src/toolkit/button.c @@ -0,0 +1,400 @@ +/* ========================================================================= */ +/** + * @file button.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "button.h" + +#include "gfxbuf.h" + +#include + +#define WLR_USE_UNSTABLE +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +static void _wlmtk_button_clicked(wlmtk_button_t *button_ptr); + +static bool _wlmtk_button_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr); +static void _wlmtk_button_element_pointer_enter( + wlmtk_element_t *element_ptr); +static void _wlmtk_button_element_pointer_leave( + wlmtk_element_t *element_ptr); + +static void apply_state(wlmtk_button_t *button_ptr); + +/* == Data ================================================================= */ + +/** Virtual method table for the button's element super class. */ +static const wlmtk_element_vmt_t button_element_vmt = { + .pointer_button = _wlmtk_button_element_pointer_button, + .pointer_enter = _wlmtk_button_element_pointer_enter, + .pointer_leave = _wlmtk_button_element_pointer_leave, +}; + +/** Virtual method table for the button. */ +static const wlmtk_button_vmt_t button_vmt = { + .clicked = _wlmtk_button_clicked, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +bool wlmtk_button_init( + wlmtk_button_t *button_ptr, + wlmtk_env_t *env_ptr) +{ + BS_ASSERT(NULL != button_ptr); + memset(button_ptr, 0, sizeof(wlmtk_button_t)); + button_ptr->vmt = button_vmt; + + if (!wlmtk_buffer_init(&button_ptr->super_buffer, env_ptr)) { + wlmtk_button_fini(button_ptr); + return false; + } + button_ptr->orig_super_element_vmt = wlmtk_element_extend( + &button_ptr->super_buffer.super_element, + &button_element_vmt); + + return true; +} + +/* ------------------------------------------------------------------------- */ +wlmtk_button_vmt_t wlmtk_button_extend( + wlmtk_button_t *button_ptr, + const wlmtk_button_vmt_t *button_vmt_ptr) +{ + wlmtk_button_vmt_t orig_vmt = button_ptr->vmt; + + if (NULL != button_vmt_ptr->clicked) { + button_ptr->vmt.clicked = button_vmt_ptr->clicked; + } + + return orig_vmt; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_button_fini(wlmtk_button_t *button_ptr) +{ + if (NULL != button_ptr->pressed_wlr_buffer_ptr) { + wlr_buffer_unlock(button_ptr->pressed_wlr_buffer_ptr); + button_ptr->pressed_wlr_buffer_ptr = NULL; + } + if (NULL != button_ptr->released_wlr_buffer_ptr) { + wlr_buffer_unlock(button_ptr->released_wlr_buffer_ptr); + button_ptr->released_wlr_buffer_ptr = NULL; + } + + wlmtk_buffer_fini(&button_ptr->super_buffer); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_button_set( + wlmtk_button_t *button_ptr, + struct wlr_buffer *released_wlr_buffer_ptr, + struct wlr_buffer *pressed_wlr_buffer_ptr ) +{ + if (NULL == released_wlr_buffer_ptr) { + BS_ASSERT(NULL == pressed_wlr_buffer_ptr); + } else { + BS_ASSERT(released_wlr_buffer_ptr->width == + pressed_wlr_buffer_ptr->width); + BS_ASSERT(released_wlr_buffer_ptr->height == + pressed_wlr_buffer_ptr->height); + } + + if (NULL != button_ptr->released_wlr_buffer_ptr) { + wlr_buffer_unlock(button_ptr->released_wlr_buffer_ptr); + } + button_ptr->released_wlr_buffer_ptr = wlr_buffer_lock( + released_wlr_buffer_ptr); + + if (NULL != button_ptr->pressed_wlr_buffer_ptr) { + wlr_buffer_unlock(button_ptr->pressed_wlr_buffer_ptr); + } + button_ptr->pressed_wlr_buffer_ptr = wlr_buffer_lock( + pressed_wlr_buffer_ptr); + + apply_state(button_ptr); +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** Default implementation of @ref wlmtk_button_vmt_t::clicked. Nothing. */ +void _wlmtk_button_clicked(__UNUSED__ wlmtk_button_t *button_ptr) +{ + // Nothing. +} + +/* ------------------------------------------------------------------------- */ +/** See @ref wlmtk_element_vmt_t::pointer_button. */ +bool _wlmtk_button_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr) +{ + wlmtk_button_t *button_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_button_t, super_buffer.super_element); + + if (button_event_ptr->button != BTN_LEFT) return false; + + switch (button_event_ptr->type) { + case WLMTK_BUTTON_DOWN: + button_ptr->pressed = true; + apply_state(button_ptr); + break; + + case WLMTK_BUTTON_UP: + button_ptr->pressed = false; + apply_state(button_ptr); + break; + + case WLMTK_BUTTON_CLICK: + button_ptr->vmt.clicked(button_ptr); + break; + + default: + break; + } + + return true; +} + +/* ------------------------------------------------------------------------- */ +/** Pointer enters the area: We may need to update visualization. */ +void _wlmtk_button_element_pointer_enter( + wlmtk_element_t *element_ptr) +{ + wlmtk_button_t *button_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_button_t, super_buffer.super_element); + button_ptr->orig_super_element_vmt.pointer_enter(element_ptr); + + apply_state(button_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Pointer leaves the area: We may need to update visualization. */ +void _wlmtk_button_element_pointer_leave( + wlmtk_element_t *element_ptr) +{ + wlmtk_button_t *button_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_button_t, super_buffer.super_element); + + button_ptr->orig_super_element_vmt.pointer_leave(element_ptr); + apply_state(button_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Sets the appropriate texture for the button. */ +void apply_state(wlmtk_button_t *button_ptr) +{ + if (button_ptr->super_buffer.super_element.pointer_inside && + button_ptr->pressed) { + wlmtk_buffer_set( + &button_ptr->super_buffer, + button_ptr->pressed_wlr_buffer_ptr); + } else { + wlmtk_buffer_set( + &button_ptr->super_buffer, + button_ptr->released_wlr_buffer_ptr); + } +} + +/* == Unit tests =========================================================== */ + +static void test_create_destroy(bs_test_t *test_ptr); +static void test_press_release(bs_test_t *test_ptr); +static void test_press_release_outside(bs_test_t *test_ptr); +static void test_press_right(bs_test_t *test_ptr); + +/** Test case definition. */ +const bs_test_case_t wlmtk_button_test_cases[] = { + { 1, "create_destroy", test_create_destroy }, + { 1, "press_release", test_press_release }, + { 1, "press_release_outside", test_press_release_outside }, + { 1, "press_right", test_press_right }, + { 0, NULL, NULL } +}; + +/** Test outcome: Whether 'clicked' was called. */ +static bool fake_button_got_clicked = false; + +/** Fake 'clicked' handler. */ +static void fake_button_clicked(__UNUSED__ wlmtk_button_t *button_ptr) { + fake_button_got_clicked = true; +} + +/** Virtual method table of fake button. */ +static const wlmtk_button_vmt_t fake_button_vmt = { + .clicked = fake_button_clicked, +}; + +/* ------------------------------------------------------------------------- */ +/** Exercises @ref wlmtk_button_init and @ref wlmtk_button_fini. */ +void test_create_destroy(bs_test_t *test_ptr) +{ + wlmtk_button_t button; + + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_button_init(&button, NULL)); + wlmtk_button_fini(&button); +} + +/* ------------------------------------------------------------------------- */ +/** Tests button pressing & releasing. */ +void test_press_release(bs_test_t *test_ptr) +{ + wlmtk_button_t button; + BS_ASSERT(wlmtk_button_init(&button, NULL)); + wlmtk_button_extend(&button, &fake_button_vmt); + + struct wlr_buffer *p_ptr = bs_gfxbuf_create_wlr_buffer(1, 1); + struct wlr_buffer *r_ptr = bs_gfxbuf_create_wlr_buffer(1, 1); + wlmtk_button_set(&button, r_ptr, p_ptr); + + wlmtk_button_event_t event = { .button = BTN_LEFT, .time_msec = 42 }; + wlmtk_element_t *element_ptr = &button.super_buffer.super_element; + fake_button_got_clicked = false; + + // Initial state: released. + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(element_ptr, 0, 0, 41)); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); + + // Button down: pressed. + event.type = WLMTK_BUTTON_DOWN; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(element_ptr, &event)); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, p_ptr); + + // Pointer leaves the area: released. + wlmtk_element_pointer_motion(element_ptr, NAN, NAN, 41); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); + + // Pointer re-enters the area: pressed. + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(element_ptr, 0, 0, 41)); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, p_ptr); + + // Button up: released. + event.type = WLMTK_BUTTON_UP; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(element_ptr, &event)); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); + + event.type = WLMTK_BUTTON_CLICK; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(element_ptr, &event)); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); + BS_TEST_VERIFY_TRUE(test_ptr, fake_button_got_clicked); + + wlr_buffer_drop(r_ptr); + wlr_buffer_drop(p_ptr); + wlmtk_button_fini(&button); +} + +/* ------------------------------------------------------------------------- */ +/** Tests button when releasing outside the pointer focus. */ +void test_press_release_outside(bs_test_t *test_ptr) +{ + wlmtk_button_t button; + BS_ASSERT(wlmtk_button_init(&button, NULL)); + + struct wlr_buffer *p_ptr = bs_gfxbuf_create_wlr_buffer(1, 1); + struct wlr_buffer *r_ptr = bs_gfxbuf_create_wlr_buffer(1, 1); + wlmtk_button_set(&button, r_ptr, p_ptr); + + wlmtk_button_event_t event = { .button = BTN_LEFT, .time_msec = 42 }; + wlmtk_element_t *element_ptr = &button.super_buffer.super_element; + + // Enter the ara. Released. + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(element_ptr, 0, 0, 41)); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); + + // Button down: pressed. + event.type = WLMTK_BUTTON_DOWN; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(element_ptr, &event)); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, p_ptr); + + // Pointer leaves the area: released. + wlmtk_element_pointer_motion(element_ptr, NAN, NAN, 41); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); + + // Button up, outside the area. Then, re-enter: Still released. + event.type = WLMTK_BUTTON_UP; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(element_ptr, &event)); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(element_ptr, 0, 0, 41)); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); + + wlr_buffer_drop(r_ptr); + wlr_buffer_drop(p_ptr); + wlmtk_button_fini(&button); +} + +/* ------------------------------------------------------------------------- */ +/** Tests button when releasing outside the pointer focus. */ +void test_press_right(bs_test_t *test_ptr) +{ + wlmtk_button_t button; + BS_ASSERT(wlmtk_button_init(&button, NULL)); + + struct wlr_buffer *p_ptr = bs_gfxbuf_create_wlr_buffer(1, 1); + struct wlr_buffer *r_ptr = bs_gfxbuf_create_wlr_buffer(1, 1); + wlmtk_button_set(&button, r_ptr, p_ptr); + + wlmtk_button_event_t event = { + .button = BTN_RIGHT, .type = WLMTK_BUTTON_DOWN, .time_msec = 42, + }; + wlmtk_element_t *element_ptr = &button.super_buffer.super_element; + + // Enter the ara. Released. + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(element_ptr, 0, 0, 41)); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); + + // Right button down: Not claimed, and remains released. + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_element_pointer_button(element_ptr, &event)); + BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); + + wlr_buffer_drop(r_ptr); + wlr_buffer_drop(p_ptr); + wlmtk_button_fini(&button); +} + +/* == End of button.c ====================================================== */ diff --git a/src/toolkit/button.h b/src/toolkit/button.h new file mode 100644 index 00000000..443ef9c6 --- /dev/null +++ b/src/toolkit/button.h @@ -0,0 +1,107 @@ +/* ========================================================================= */ +/** + * @file button.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_BUTTON_H__ +#define __WLMTK_BUTTON_H__ + +#include "buffer.h" +#include "element.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Forward declaration: State of a button. */ +typedef struct _wlmtk_button_t wlmtk_button_t; + +/** Virtual method table of the button. */ +typedef struct { + /** Optional: Called when the button has been clicked. */ + void (*clicked)(wlmtk_button_t *button_ptr); +} wlmtk_button_vmt_t; + +/** State of a button. */ +struct _wlmtk_button_t { + /** Super class of the button: A buffer. */ + wlmtk_buffer_t super_buffer; + /** Original virtual method table of the superclass element. */ + wlmtk_element_vmt_t orig_super_element_vmt; + /** The virtual method table. */ + wlmtk_button_vmt_t vmt; + + /** WLR buffer holding the button in released state. */ + struct wlr_buffer *released_wlr_buffer_ptr; + /** WLR buffer holding the button in pressed state. */ + struct wlr_buffer *pressed_wlr_buffer_ptr; + + /** Whether the button is currently pressed. */ + bool pressed; +}; + +/** + * Initializes the button. + * + * @param button_ptr + * @param env_ptr + * + * @return true on success. + */ +bool wlmtk_button_init(wlmtk_button_t *button_ptr, + wlmtk_env_t *env_ptr); + +/** + * Extends the button's virtual methods. + * + * @param button_ptr + * @param button_vmt_ptr + * + * @return The original virtual method table. + */ +wlmtk_button_vmt_t wlmtk_button_extend( + wlmtk_button_t *button_ptr, + const wlmtk_button_vmt_t *button_vmt_ptr); + +/** + * Cleans up the button. + * + * @param button_ptr + */ +void wlmtk_button_fini(wlmtk_button_t *button_ptr); + +/** + * Sets (or updates) the button textures. + * + * @param button_ptr + * @param released_wlr_buffer_ptr + * @param pressed_wlr_buffer_ptr + */ +void wlmtk_button_set( + wlmtk_button_t *button_ptr, + struct wlr_buffer *released_wlr_buffer_ptr, + struct wlr_buffer *pressed_wlr_buffer_ptr); + +/** Unit test cases. */ +extern const bs_test_case_t wlmtk_button_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_BUTTON_H__ */ +/* == End of button.h ====================================================== */ diff --git a/src/toolkit/container.c b/src/toolkit/container.c new file mode 100644 index 00000000..e226619c --- /dev/null +++ b/src/toolkit/container.c @@ -0,0 +1,1436 @@ +/* ========================================================================= */ +/** + * @file container.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "container.h" + +#include "util.h" + +#define WLR_USE_UNSTABLE +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +static struct wlr_scene_node *_wlmtk_container_element_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr); +static void _wlmtk_container_element_get_dimensions( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr); +static void _wlmtk_container_element_get_pointer_area( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr); +static bool _wlmtk_container_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, double y, + uint32_t time_msec); +static bool _wlmtk_container_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr); +static void _wlmtk_container_element_pointer_enter( + wlmtk_element_t *element_ptr); + +static void handle_wlr_scene_tree_node_destroy( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr); +static bool update_pointer_focus_at( + wlmtk_container_t *container_ptr, + double x, + double y, + uint32_t time_msec); +static void _wlmtk_container_update_layout(wlmtk_container_t *container_ptr); + +/** Virtual method table for the container's super class: Element. */ +static const wlmtk_element_vmt_t container_element_vmt = { + .create_scene_node = _wlmtk_container_element_create_scene_node, + .get_dimensions = _wlmtk_container_element_get_dimensions, + .get_pointer_area = _wlmtk_container_element_get_pointer_area, + .pointer_motion = _wlmtk_container_element_pointer_motion, + .pointer_button = _wlmtk_container_element_pointer_button, + .pointer_enter = _wlmtk_container_element_pointer_enter, +}; + +/** Default virtual method table. Initializes non-abstract methods. */ +static const wlmtk_container_vmt_t container_vmt = { + .update_layout = _wlmtk_container_update_layout, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +bool wlmtk_container_init( + wlmtk_container_t *container_ptr, + wlmtk_env_t *env_ptr) +{ + BS_ASSERT(NULL != container_ptr); + memset(container_ptr, 0, sizeof(wlmtk_container_t)); + container_ptr->vmt = container_vmt; + + if (!wlmtk_element_init(&container_ptr->super_element, env_ptr)) { + return false; + } + container_ptr->orig_super_element_vmt = wlmtk_element_extend( + &container_ptr->super_element, &container_element_vmt); + + return true; +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_container_init_attached( + wlmtk_container_t *container_ptr, + wlmtk_env_t *env_ptr, + struct wlr_scene_tree *root_wlr_scene_tree_ptr) +{ + if (!wlmtk_container_init(container_ptr, env_ptr)) return false; + + container_ptr->super_element.wlr_scene_node_ptr = + _wlmtk_container_element_create_scene_node( + &container_ptr->super_element, root_wlr_scene_tree_ptr); + if (NULL == container_ptr->super_element.wlr_scene_node_ptr) { + wlmtk_container_fini(container_ptr); + return false; + } + + BS_ASSERT(NULL != container_ptr->super_element.wlr_scene_node_ptr); + return true; +} + +/* ------------------------------------------------------------------------- */ +wlmtk_container_vmt_t wlmtk_container_extend( + wlmtk_container_t *container_ptr, + const wlmtk_container_vmt_t *container_vmt_ptr) +{ + wlmtk_container_vmt_t orig_vmt = container_ptr->vmt; + + if (NULL != container_vmt_ptr->update_layout) { + container_ptr->vmt.update_layout = container_vmt_ptr->update_layout; + } + return orig_vmt; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_container_fini(wlmtk_container_t *container_ptr) +{ + bs_dllist_node_t *dlnode_ptr; + while (NULL != (dlnode_ptr = container_ptr->elements.head_ptr)) { + wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); + wlmtk_container_remove_element(container_ptr, element_ptr); + BS_ASSERT(container_ptr->elements.head_ptr != dlnode_ptr); + wlmtk_element_destroy(element_ptr); + } + + // For containers created with wlmtk_container_init_attached(): We also + // need to remove references to the WLR scene tree. + if (NULL != container_ptr->wlr_scene_tree_ptr) { + BS_ASSERT(NULL == container_ptr->super_element.parent_container_ptr); + wlr_scene_node_destroy(&container_ptr->wlr_scene_tree_ptr->node); + container_ptr->wlr_scene_tree_ptr = NULL; + container_ptr->super_element.wlr_scene_node_ptr = NULL; + } + + wlmtk_element_fini(&container_ptr->super_element); + memset(container_ptr, 0, sizeof(wlmtk_container_t)); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_container_add_element( + wlmtk_container_t *container_ptr, + wlmtk_element_t *element_ptr) +{ + BS_ASSERT(NULL == element_ptr->parent_container_ptr); + BS_ASSERT(NULL == element_ptr->wlr_scene_node_ptr); + + bs_dllist_push_front( + &container_ptr->elements, + wlmtk_dlnode_from_element(element_ptr)); + wlmtk_element_set_parent_container(element_ptr, container_ptr); + + wlmtk_container_update_layout(container_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_container_add_element_atop( + wlmtk_container_t *container_ptr, + wlmtk_element_t *reference_element_ptr, + wlmtk_element_t *element_ptr) +{ + BS_ASSERT(NULL == element_ptr->parent_container_ptr); + BS_ASSERT(NULL == element_ptr->wlr_scene_node_ptr); + BS_ASSERT( + NULL == reference_element_ptr || + container_ptr == reference_element_ptr->parent_container_ptr); + + if (NULL == reference_element_ptr) { + bs_dllist_push_back( + &container_ptr->elements, + wlmtk_dlnode_from_element(element_ptr)); + } else { + bs_dllist_insert_node_before( + &container_ptr->elements, + wlmtk_dlnode_from_element(reference_element_ptr), + wlmtk_dlnode_from_element(element_ptr)); + } + + wlmtk_element_set_parent_container(element_ptr, container_ptr); + if (NULL != element_ptr->wlr_scene_node_ptr) { + + if (NULL == reference_element_ptr) { + wlr_scene_node_lower_to_bottom(element_ptr->wlr_scene_node_ptr); + } else { + BS_ASSERT(NULL != reference_element_ptr->wlr_scene_node_ptr); + wlr_scene_node_place_above( + element_ptr->wlr_scene_node_ptr, + reference_element_ptr->wlr_scene_node_ptr); + } + } + wlmtk_container_update_layout(container_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_container_remove_element( + wlmtk_container_t *container_ptr, + wlmtk_element_t *element_ptr) +{ + BS_ASSERT(element_ptr->parent_container_ptr == container_ptr); + + wlmtk_element_set_parent_container(element_ptr, NULL); + bs_dllist_remove( + &container_ptr->elements, + wlmtk_dlnode_from_element(element_ptr)); + + if (container_ptr->left_button_element_ptr == element_ptr) { + container_ptr->left_button_element_ptr = NULL; + } + + wlmtk_container_update_layout(container_ptr); + BS_ASSERT(element_ptr != container_ptr->pointer_focus_element_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_container_raise_element_to_top( + wlmtk_container_t *container_ptr, + wlmtk_element_t *element_ptr) +{ + BS_ASSERT(element_ptr->parent_container_ptr == container_ptr); + + // Already at the top? Nothing to do. + if (wlmtk_dlnode_from_element(element_ptr) == + container_ptr->elements.head_ptr) return; + + bs_dllist_remove( + &container_ptr->elements, + wlmtk_dlnode_from_element(element_ptr)); + bs_dllist_push_front( + &container_ptr->elements, + wlmtk_dlnode_from_element(element_ptr)); + + if (NULL != element_ptr->wlr_scene_node_ptr) { + wlr_scene_node_raise_to_top(element_ptr->wlr_scene_node_ptr); + } + + wlmtk_container_update_layout(container_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_container_update_pointer_focus(wlmtk_container_t *container_ptr) +{ + if (NULL != container_ptr->super_element.parent_container_ptr) { + wlmtk_container_update_pointer_focus( + container_ptr->super_element.parent_container_ptr); + } else { + update_pointer_focus_at( + container_ptr, + container_ptr->super_element.last_pointer_x, + container_ptr->super_element.last_pointer_y, + container_ptr->super_element.last_pointer_time_msec); + } +} + +/* ------------------------------------------------------------------------- */ +struct wlr_scene_tree *wlmtk_container_wlr_scene_tree( + wlmtk_container_t *container_ptr) +{ + return container_ptr->wlr_scene_tree_ptr; +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** + * Implementation of the superclass wlmtk_element_t::create_scene_node method. + * + * Creates the wlroots scene graph tree for the container, and will attach all + * already-contained elements to the scene graph, as well. + * + * @param element_ptr + * @param wlr_scene_tree_ptr + * + * @return Pointer to the scene graph API node. + */ +struct wlr_scene_node *_wlmtk_container_element_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr) +{ + wlmtk_container_t *container_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_container_t, super_element); + + BS_ASSERT(NULL == container_ptr->wlr_scene_tree_ptr); + container_ptr->wlr_scene_tree_ptr = wlr_scene_tree_create( + wlr_scene_tree_ptr); + BS_ASSERT(NULL != container_ptr->wlr_scene_tree_ptr); + + // Build the nodes from tail to head: Adding an element to the scene graph + // will always put it on top, so this adds the elements in desired order. + for (bs_dllist_node_t *dlnode_ptr = container_ptr->elements.tail_ptr; + dlnode_ptr != NULL; + dlnode_ptr = dlnode_ptr->prev_ptr) { + wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); + BS_ASSERT(NULL == element_ptr->wlr_scene_node_ptr); + wlmtk_element_attach_to_scene_graph(element_ptr); + } + + wlmtk_util_connect_listener_signal( + &container_ptr->wlr_scene_tree_ptr->node.events.destroy, + &container_ptr->wlr_scene_tree_node_destroy_listener, + handle_wlr_scene_tree_node_destroy); + return &container_ptr->wlr_scene_tree_ptr->node; +} + +/* ------------------------------------------------------------------------- */ +/** + * Implementation of the element's get_dimensions method: Return dimensions. + * + * @param element_ptr + * @param left_ptr Leftmost position. May be NULL. + * @param top_ptr Topmost position. May be NULL. + * @param right_ptr Rightmost position. Ma be NULL. + * @param bottom_ptr Bottommost position. May be NULL. + */ +void _wlmtk_container_element_get_dimensions( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr) +{ + wlmtk_container_t *container_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_container_t, super_element); + + int left = INT32_MAX, top = INT32_MAX; + int right = INT32_MIN, bottom = INT32_MIN; + for (bs_dllist_node_t *dlnode_ptr = container_ptr->elements.head_ptr; + dlnode_ptr != NULL; + dlnode_ptr = dlnode_ptr->next_ptr) { + wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); + if (!element_ptr->visible) continue; + + int x_pos, y_pos; + wlmtk_element_get_position(element_ptr, &x_pos, &y_pos); + int x1, y1, x2, y2; + wlmtk_element_get_dimensions(element_ptr, &x1, &y1, &x2, &y2); + left = BS_MIN(left, x_pos + x1); + top = BS_MIN(top, y_pos + y1); + right = BS_MAX(right, x_pos + x2); + bottom = BS_MAX(bottom, y_pos + y2); + } + + if (left >= right) { left = 0; right = 0; } + if (top >= bottom) { top = 0; bottom = 0; } + + if (NULL != left_ptr) *left_ptr = left; + if (NULL != top_ptr) *top_ptr = top; + if (NULL != right_ptr) *right_ptr = right; + if (NULL != bottom_ptr) *bottom_ptr = bottom; +} + +/* ------------------------------------------------------------------------- */ +/** + * Returns the minimal rectangle covering all element's pointer areas. + * + * @param element_ptr + * @param left_ptr Leftmost position. May be NULL. + * @param top_ptr Topmost position. May be NULL. + * @param right_ptr Rightmost position. Ma be NULL. + * @param bottom_ptr Bottommost position. May be NULL. + */ +void _wlmtk_container_element_get_pointer_area( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr) +{ + wlmtk_container_t *container_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_container_t, super_element); + + int left = INT32_MAX, top = INT32_MAX; + int right = INT32_MIN, bottom = INT32_MIN; + for (bs_dllist_node_t *dlnode_ptr = container_ptr->elements.head_ptr; + dlnode_ptr != NULL; + dlnode_ptr = dlnode_ptr->next_ptr) { + wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); + if (!element_ptr->visible) continue; + + int x_pos, y_pos; + wlmtk_element_get_position(element_ptr, &x_pos, &y_pos); + int x1, y1, x2, y2; + wlmtk_element_get_pointer_area(element_ptr, &x1, &y1, &x2, &y2); + left = BS_MIN(left, x_pos + x1); + top = BS_MIN(top, y_pos + y1); + right = BS_MAX(right, x_pos + x2); + bottom = BS_MAX(bottom, y_pos + y2); + } + + if (left >= right) { left = 0; right = 0; } + if (top >= bottom) { top = 0; bottom = 0; } + + if (NULL != left_ptr) *left_ptr = left; + if (NULL != top_ptr) *top_ptr = top; + if (NULL != right_ptr) *right_ptr = right; + if (NULL != bottom_ptr) *bottom_ptr = bottom; +} + +/* ------------------------------------------------------------------------- */ +/** + * Implementation of the element's motion method: Handle pointer moves. + * + * @param element_ptr + * @param x + * @param y + * @param time_msec + * + * @return Whether this container has an element that accepts the emotion. + */ +bool _wlmtk_container_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, + double y, + uint32_t time_msec) +{ + wlmtk_container_t *container_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_container_t, super_element); + container_ptr->orig_super_element_vmt.pointer_motion( + element_ptr, x, y, time_msec); + + return update_pointer_focus_at(container_ptr, x, y, time_msec); +} + +/* ------------------------------------------------------------------------- */ +/** + * Implementation of the element's pointer_button() method. Forwards it to the + * element currently having pointer focus. + * + * @param element_ptr + * @param button_event_ptr + * + * @return true if the button was handled. + */ +bool _wlmtk_container_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr) +{ + wlmtk_container_t *container_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_container_t, super_element); + bool accepted = false; + + // TODO: Generalize this for non-LEFT buttons. + if (BTN_LEFT == button_event_ptr->button) { + + switch (button_event_ptr->type) { + case WLMTK_BUTTON_DOWN: + // Forward to the pointer focus element, if any. If it + // was accepted: remember the element. + if (NULL != container_ptr->pointer_focus_element_ptr) { + accepted = wlmtk_element_pointer_button( + container_ptr->pointer_focus_element_ptr, + button_event_ptr); + if (accepted) { + container_ptr->left_button_element_ptr = + container_ptr->pointer_focus_element_ptr; + } else { + container_ptr->left_button_element_ptr = NULL; + } + } + break; + + case WLMTK_BUTTON_UP: + // Forward to the element that received the DOWN, if any. + if (NULL != container_ptr->left_button_element_ptr) { + accepted = wlmtk_element_pointer_button( + container_ptr->left_button_element_ptr, + button_event_ptr); + } + break; + + case WLMTK_BUTTON_CLICK: + case WLMTK_BUTTON_DOUBLE_CLICK: + // Will only be forwarded, if the element still (or again) + // has pointer focus. + if (NULL != container_ptr->left_button_element_ptr && + container_ptr->left_button_element_ptr == + container_ptr->pointer_focus_element_ptr) { + accepted = wlmtk_element_pointer_button( + container_ptr->left_button_element_ptr, + button_event_ptr); + } + break; + + + default: // Uh, don't know about this... + bs_log(BS_FATAL, "Unhandled button type %d", + button_event_ptr->type); + } + + return accepted; + } + + + if (NULL == container_ptr->pointer_focus_element_ptr) return false; + + return wlmtk_element_pointer_button( + container_ptr->pointer_focus_element_ptr, + button_event_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Handler for when the pointer enters the area. Nothing for container. */ +void _wlmtk_container_element_pointer_enter( + __UNUSED__ wlmtk_element_t *element_ptr) +{ + // Nothing. Do not call parent. +} + +/* ------------------------------------------------------------------------- */ +/** + * Handles the 'destroy' callback of wlr_scene_tree_ptr->node. + * + * Will also detach (but not destroy) each of the still-contained elements. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_wlr_scene_tree_node_destroy( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_container_t *container_ptr = BS_CONTAINER_OF( + listener_ptr, wlmtk_container_t, wlr_scene_tree_node_destroy_listener); + + container_ptr->wlr_scene_tree_ptr = NULL; + for (bs_dllist_node_t *dlnode_ptr = container_ptr->elements.head_ptr; + dlnode_ptr != NULL; + dlnode_ptr = dlnode_ptr->next_ptr) { + wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); + // Will read the parent container's wlr_scene_tree_ptr == NULL. + wlmtk_element_attach_to_scene_graph(element_ptr); + } + + // Since this is a callback from the tree node dtor, the tree is going to + // be destroyed. We are using this to reset the container's reference. + wl_list_remove(&container_ptr->wlr_scene_tree_node_destroy_listener.link); +} + +/* ------------------------------------------------------------------------- */ +/** + * Updates pointer focus of container for position (x, y). + * + * Updates wlmtk_container_t::pointer_focus_element_ptr. + * + * @param container_ptr + * @param x + * @param y + * @param time_msec + * + * @return Whether there was an element acception the motion at (x, y). + */ +bool update_pointer_focus_at( + wlmtk_container_t *container_ptr, + double x, + double y, + uint32_t time_msec) +{ + for (bs_dllist_node_t *dlnode_ptr = container_ptr->elements.head_ptr; + dlnode_ptr != NULL; + dlnode_ptr = dlnode_ptr->next_ptr) { + wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); + + if (!element_ptr->visible) continue; + + int x_pos, y_pos; + wlmtk_element_get_position(element_ptr, &x_pos, &y_pos); + int x1, y1, x2, y2; + wlmtk_element_get_pointer_area(element_ptr, &x1, &y1, &x2, &y2); + if (x_pos + x1 <= x && x < x_pos + x2 && + y_pos + y1 <= y && y < y_pos + y2) { + if (!wlmtk_element_pointer_motion( + element_ptr, x - x_pos, y - y_pos, time_msec)) { + continue; + } + + // There is a focus change. Invalidate coordinates in old element. + if (container_ptr->pointer_focus_element_ptr != element_ptr && + NULL != container_ptr->pointer_focus_element_ptr) { + wlmtk_element_pointer_motion( + container_ptr->pointer_focus_element_ptr, + NAN, NAN, time_msec); + + } + container_ptr->pointer_focus_element_ptr = element_ptr; + return true; + } + } + + // Getting here implies we didn't have an element catching the motion, + // so it must have happened outside our araea. We also should reset + // pointer focus element now. + if (NULL != container_ptr->pointer_focus_element_ptr) { + wlmtk_element_pointer_motion( + container_ptr->pointer_focus_element_ptr, + NAN, NAN, time_msec); + container_ptr->pointer_focus_element_ptr = NULL; + } + return false; +} + +/* ------------------------------------------------------------------------- */ +/** + * Base implementation of wlmtk_container_vmt_t::update_layout. If there's + * a parent, will call @ref wlmtk_container_update_layout. Otherwise, will + * update the pointer focus. + * + * @param container_ptr + */ +void _wlmtk_container_update_layout(wlmtk_container_t *container_ptr) +{ + if (NULL != container_ptr->super_element.parent_container_ptr) { + wlmtk_container_update_layout( + container_ptr->super_element.parent_container_ptr); + } else { + wlmtk_container_update_pointer_focus(container_ptr); + } +} + +/* == Helper for unit tests: A fake container with a tree, as parent ======= */ + +/** State of the "fake" parent container. Refers to a scene graph. */ +typedef struct { + /** The actual container */ + wlmtk_container_t container; + /** A scene graph. Not attached to any output, */ + struct wlr_scene *wlr_scene_ptr; +} fake_parent_container_t; + +/* ------------------------------------------------------------------------- */ +wlmtk_container_t *wlmtk_container_create_fake_parent(void) +{ + fake_parent_container_t *fake_parent_container_ptr = logged_calloc( + 1, sizeof(fake_parent_container_t)); + if (NULL == fake_parent_container_ptr) return NULL; + + fake_parent_container_ptr->wlr_scene_ptr = wlr_scene_create(); + if (NULL == fake_parent_container_ptr->wlr_scene_ptr) { + wlmtk_container_destroy_fake_parent( + &fake_parent_container_ptr->container); + return NULL; + } + + if (!wlmtk_container_init_attached( + &fake_parent_container_ptr->container, + NULL, + &fake_parent_container_ptr->wlr_scene_ptr->tree)) { + wlmtk_container_destroy_fake_parent( + &fake_parent_container_ptr->container); + return NULL; + } + + return &fake_parent_container_ptr->container; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_container_destroy_fake_parent(wlmtk_container_t *container_ptr) +{ + fake_parent_container_t *fake_parent_container_ptr = BS_CONTAINER_OF( + container_ptr, fake_parent_container_t, container); + + wlmtk_container_fini(&fake_parent_container_ptr->container); + + if (NULL != fake_parent_container_ptr->wlr_scene_ptr) { + // Note: There is no "wlr_scene_destroy()" method; as of 2023-09-02, + // this is just a flat allocated struct. + free(fake_parent_container_ptr->wlr_scene_ptr); + fake_parent_container_ptr->wlr_scene_ptr = NULL; + } + + free(fake_parent_container_ptr); +} + +/* == Unit tests =========================================================== */ + +static void test_init_fini(bs_test_t *test_ptr); +static void test_add_remove(bs_test_t *test_ptr); +static void test_add_remove_with_scene_graph(bs_test_t *test_ptr); +static void test_add_with_raise(bs_test_t *test_ptr); +static void test_pointer_motion(bs_test_t *test_ptr); +static void test_pointer_focus(bs_test_t *test_ptr); +static void test_pointer_focus_move(bs_test_t *test_ptr); +static void test_pointer_focus_layered(bs_test_t *test_ptr); +static void test_pointer_button(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_container_test_cases[] = { + { 1, "init_fini", test_init_fini }, + { 1, "add_remove", test_add_remove }, + { 1, "add_remove_with_scene_graph", test_add_remove_with_scene_graph }, + { 1, "add_with_raise", test_add_with_raise }, + { 1, "pointer_motion", test_pointer_motion }, + { 1, "pointer_focus", test_pointer_focus }, + { 1, "pointer_focus_move", test_pointer_focus_move }, + { 1, "pointer_focus_layered", test_pointer_focus_layered }, + { 1, "pointer_button", test_pointer_button }, + { 0, NULL, NULL } +}; + +/* ------------------------------------------------------------------------- */ +/** Exercises init() and fini() methods, verifies dtor forwarding. */ +void test_init_fini(bs_test_t *test_ptr) +{ + wlmtk_container_t container; + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_container_init(&container, NULL)); + // Also expect the super element to be initialized. + BS_TEST_VERIFY_NEQ( + test_ptr, NULL, container.super_element.vmt.pointer_motion); + + wlmtk_container_fini(&container); + // Also expect the super element to be un-initialized. + BS_TEST_VERIFY_EQ( + test_ptr, NULL, container.super_element.vmt.pointer_motion); +} + +/* ------------------------------------------------------------------------- */ +/** Exercises adding and removing elements, verifies destruction on fini. */ +void test_add_remove(bs_test_t *test_ptr) +{ + wlmtk_container_t container; + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_container_init(&container, NULL)); + + wlmtk_fake_element_t *elem1_ptr, *elem2_ptr, *elem3_ptr; + elem1_ptr = wlmtk_fake_element_create(); + BS_ASSERT(NULL != elem1_ptr); + elem2_ptr = wlmtk_fake_element_create(); + BS_ASSERT(NULL != elem2_ptr); + elem3_ptr = wlmtk_fake_element_create(); + BS_ASSERT(NULL != elem3_ptr); + + // Build sequence: 3 -> 2 -> 1. + wlmtk_container_add_element(&container, &elem1_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, &container, elem1_ptr->element.parent_container_ptr); + wlmtk_container_add_element(&container, &elem2_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, &container, elem2_ptr->element.parent_container_ptr); + wlmtk_container_add_element(&container, &elem3_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, &container, elem3_ptr->element.parent_container_ptr); + + // Remove 2, then add at the bottom: 3 -> 1 -> 2. + wlmtk_container_remove_element(&container, &elem2_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, NULL, elem2_ptr->element.parent_container_ptr); + wlmtk_container_add_element_atop(&container, NULL, &elem2_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, &container, elem2_ptr->element.parent_container_ptr); + BS_TEST_VERIFY_EQ( + test_ptr, + wlmtk_dlnode_from_element(&elem1_ptr->element)->next_ptr, + wlmtk_dlnode_from_element(&elem2_ptr->element)); + + // Remove elem3 and add atop elem2: 1 -> 3 -> 2. + wlmtk_container_remove_element(&container, &elem3_ptr->element); + wlmtk_container_add_element_atop( + &container, &elem2_ptr->element, &elem3_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, + wlmtk_dlnode_from_element(&elem3_ptr->element)->next_ptr, + wlmtk_dlnode_from_element(&elem2_ptr->element)); + + wlmtk_container_remove_element(&container, &elem2_ptr->element); + wlmtk_element_destroy(&elem2_ptr->element); + + // Will destroy contained elements. + wlmtk_container_fini(&container); +} + +/* ------------------------------------------------------------------------- */ +/** Tests that elements are attached, resp. detached from scene graph. */ +void test_add_remove_with_scene_graph(bs_test_t *test_ptr) +{ + wlmtk_container_t *fake_parent_ptr = wlmtk_container_create_fake_parent(); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fake_parent_ptr); + wlmtk_container_t container; + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_container_init(&container, NULL)); + + wlmtk_fake_element_t *fe3_ptr = wlmtk_fake_element_create(); + wlmtk_container_add_element(&container, &fe3_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fe3_ptr->element.wlr_scene_node_ptr); + wlmtk_fake_element_t *fe2_ptr = wlmtk_fake_element_create(); + wlmtk_container_add_element(&container, &fe2_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fe2_ptr->element.wlr_scene_node_ptr); + + wlmtk_element_set_parent_container( + &container.super_element, fake_parent_ptr); + + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fe3_ptr->element.wlr_scene_node_ptr); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fe2_ptr->element.wlr_scene_node_ptr); + + BS_TEST_VERIFY_EQ( + test_ptr, + container.elements.head_ptr, &fe2_ptr->element.dlnode); + BS_TEST_VERIFY_EQ( + test_ptr, + container.elements.tail_ptr, &fe3_ptr->element.dlnode); + + // The top is at parent->children.prev (see wlr_scene_node_raise_to_top). + // Seems counter-intuitive, since wayhland-util.h denotes `prev` to refer + // to the last element in the list. + BS_TEST_VERIFY_EQ( + test_ptr, + container.wlr_scene_tree_ptr->children.prev, + &fe2_ptr->element.wlr_scene_node_ptr->link); + BS_TEST_VERIFY_EQ( + test_ptr, + container.wlr_scene_tree_ptr->children.prev->prev, + &fe3_ptr->element.wlr_scene_node_ptr->link); + + // Want to have the node. + BS_TEST_VERIFY_NEQ( + test_ptr, NULL, container.super_element.wlr_scene_node_ptr); + + // Fresh element: No scene graph node yet. + wlmtk_fake_element_t *fe0_ptr = wlmtk_fake_element_create(); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fe0_ptr->element.wlr_scene_node_ptr); + + // Add to container with attached graph: Element now has a graph node. + wlmtk_container_add_element(&container, &fe0_ptr->element); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fe0_ptr->element.wlr_scene_node_ptr); + + // Now fe0 has to be on top, followed by fe2 and fe3. + BS_TEST_VERIFY_EQ( + test_ptr, + container.wlr_scene_tree_ptr->children.prev, + &fe0_ptr->element.wlr_scene_node_ptr->link); + BS_TEST_VERIFY_EQ( + test_ptr, + container.wlr_scene_tree_ptr->children.prev->prev, + &fe2_ptr->element.wlr_scene_node_ptr->link); + BS_TEST_VERIFY_EQ( + test_ptr, + container.wlr_scene_tree_ptr->children.prev->prev->prev, + &fe3_ptr->element.wlr_scene_node_ptr->link); + + // One more element, but we add this atop of fe2. + wlmtk_fake_element_t *fe1_ptr = wlmtk_fake_element_create(); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fe1_ptr->element.wlr_scene_node_ptr); + wlmtk_container_add_element_atop( + &container, &fe2_ptr->element, &fe1_ptr->element); + + BS_TEST_VERIFY_EQ( + test_ptr, + container.wlr_scene_tree_ptr->children.prev, + &fe0_ptr->element.wlr_scene_node_ptr->link); + BS_TEST_VERIFY_EQ( + test_ptr, + container.wlr_scene_tree_ptr->children.prev->prev, + &fe1_ptr->element.wlr_scene_node_ptr->link); + BS_TEST_VERIFY_EQ( + test_ptr, + container.wlr_scene_tree_ptr->children.prev->prev->prev, + &fe2_ptr->element.wlr_scene_node_ptr->link); + BS_TEST_VERIFY_EQ( + test_ptr, + container.wlr_scene_tree_ptr->children.prev->prev->prev->prev, + &fe3_ptr->element.wlr_scene_node_ptr->link); + + // Remove: The element's graph node must be destroyed & cleared.. + wlmtk_container_remove_element(&container, &fe0_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fe0_ptr->element.wlr_scene_node_ptr); + wlmtk_element_destroy(&fe0_ptr->element); + + wlmtk_element_set_parent_container(&container.super_element, NULL); + + BS_TEST_VERIFY_EQ(test_ptr, NULL, fe3_ptr->element.wlr_scene_node_ptr); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fe2_ptr->element.wlr_scene_node_ptr); + + wlmtk_container_remove_element(&container, &fe3_ptr->element); + wlmtk_element_destroy(&fe3_ptr->element); + wlmtk_container_remove_element(&container, &fe2_ptr->element); + wlmtk_element_destroy(&fe2_ptr->element); + + wlmtk_container_fini(&container); + wlmtk_container_destroy_fake_parent(fake_parent_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests that elements inserted at position are also placed in scene graph. */ +void test_add_with_raise(bs_test_t *test_ptr) +{ + wlmtk_container_t *c_ptr = wlmtk_container_create_fake_parent(); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, c_ptr); + + // fe1 added. Sole element, is the top. + wlmtk_fake_element_t *fe1_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&fe1_ptr->element, true); + wlmtk_container_add_element(c_ptr, &fe1_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, + c_ptr->wlr_scene_tree_ptr->children.prev, + &fe1_ptr->element.wlr_scene_node_ptr->link); + + wlmtk_element_pointer_motion(&c_ptr->super_element, 0, 0, 7); + BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_motion_called); + fe1_ptr->pointer_motion_called = false; + BS_TEST_VERIFY_EQ( + test_ptr, &fe1_ptr->element, c_ptr->pointer_focus_element_ptr); + + // fe2 placed atop 'NULL', goes to back. + wlmtk_fake_element_t *fe2_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&fe2_ptr->element, true); + wlmtk_container_add_element_atop(c_ptr, NULL, &fe2_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, + c_ptr->wlr_scene_tree_ptr->children.prev->prev, + &fe2_ptr->element.wlr_scene_node_ptr->link); + + // Raise fe2. + wlmtk_container_raise_element_to_top(c_ptr, &fe2_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, + c_ptr->wlr_scene_tree_ptr->children.prev, + &fe2_ptr->element.wlr_scene_node_ptr->link); + BS_TEST_VERIFY_EQ( + test_ptr, + c_ptr->wlr_scene_tree_ptr->children.prev->prev, + &fe1_ptr->element.wlr_scene_node_ptr->link); + + // Must also update pointer focus. + BS_TEST_VERIFY_EQ( + test_ptr, &fe2_ptr->element, c_ptr->pointer_focus_element_ptr); + BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_motion_called); + fe2_ptr->pointer_motion_called = false; + + // Now remove fe1 and add on top of fe2. Ensure scene graph has fe1 on top + // and pointer focus is on it, too. + wlmtk_container_remove_element(c_ptr, &fe1_ptr->element); + wlmtk_container_add_element_atop(c_ptr, &fe2_ptr->element, &fe1_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, + c_ptr->wlr_scene_tree_ptr->children.prev, + &fe1_ptr->element.wlr_scene_node_ptr->link); + BS_TEST_VERIFY_EQ( + test_ptr, + c_ptr->wlr_scene_tree_ptr->children.prev->prev, + &fe2_ptr->element.wlr_scene_node_ptr->link); + BS_TEST_VERIFY_EQ( + test_ptr, &fe1_ptr->element, c_ptr->pointer_focus_element_ptr); + BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_motion_called); + + wlmtk_container_remove_element(c_ptr, &fe2_ptr->element); + wlmtk_element_destroy(&fe2_ptr->element); + wlmtk_container_remove_element(c_ptr, &fe1_ptr->element); + wlmtk_element_destroy(&fe1_ptr->element); + + wlmtk_container_destroy_fake_parent(c_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests the 'motion' method for container. */ +void test_pointer_motion(bs_test_t *test_ptr) +{ + wlmtk_container_t container; + BS_ASSERT(wlmtk_container_init(&container, NULL)); + wlmtk_element_set_visible(&container.super_element, true); + + // Note: pointer area extends by (-1, -2, 3, 4) on each fake element. + wlmtk_fake_element_t *elem1_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_position(&elem1_ptr->element, -20, -40); + elem1_ptr->dimensions.width = 10; + elem1_ptr->dimensions.height = 5; + wlmtk_element_set_visible(&elem1_ptr->element, false); + wlmtk_container_add_element(&container, &elem1_ptr->element); + wlmtk_fake_element_t *elem2_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_position(&elem2_ptr->element, 100, 200); + elem2_ptr->dimensions.width = 10; + elem2_ptr->dimensions.height = 5; + wlmtk_element_set_visible(&elem2_ptr->element, true); + wlmtk_container_add_element(&container, &elem2_ptr->element); + + // Verify 'dimensions' and 'pointer_area', derived from children. + int l, t, r, b; + wlmtk_element_get_dimensions(&container.super_element, &l, &t, &r, &b); + BS_TEST_VERIFY_EQ(test_ptr, 100, l); + BS_TEST_VERIFY_EQ(test_ptr, 200, t); + BS_TEST_VERIFY_EQ(test_ptr, 110, r); + BS_TEST_VERIFY_EQ(test_ptr, 205, b); + + wlmtk_element_set_visible(&elem1_ptr->element, true); + wlmtk_element_get_dimensions(&container.super_element, &l, &t, &r, &b); + BS_TEST_VERIFY_EQ(test_ptr, -20, l); + BS_TEST_VERIFY_EQ(test_ptr, -40, t); + BS_TEST_VERIFY_EQ(test_ptr, 110, r); + BS_TEST_VERIFY_EQ(test_ptr, 205, b); + + wlmtk_element_set_visible(&elem1_ptr->element, false); + wlmtk_element_get_pointer_area(&container.super_element, &l, &t, &r, &b); + BS_TEST_VERIFY_EQ(test_ptr, 99, l); + BS_TEST_VERIFY_EQ(test_ptr, 198, t); + BS_TEST_VERIFY_EQ(test_ptr, 113, r); + BS_TEST_VERIFY_EQ(test_ptr, 209, b); + + wlmtk_element_set_visible(&elem1_ptr->element, true); + wlmtk_element_get_pointer_area(&container.super_element, &l, &t, &r, &b); + BS_TEST_VERIFY_EQ(test_ptr, -21, l); + BS_TEST_VERIFY_EQ(test_ptr, -42, t); + BS_TEST_VERIFY_EQ(test_ptr, 113, r); + BS_TEST_VERIFY_EQ(test_ptr, 209, b); + + // Same must hold for the parent container. + wlmtk_container_t parent_container; + BS_ASSERT(wlmtk_container_init(&parent_container, NULL)); + wlmtk_container_add_element(&parent_container, + &container.super_element); + + wlmtk_element_get_dimensions( + &parent_container.super_element, &l, &t, &r, &b); + BS_TEST_VERIFY_EQ(test_ptr, -20, l); + BS_TEST_VERIFY_EQ(test_ptr, -40, t); + BS_TEST_VERIFY_EQ(test_ptr, 110, r); + BS_TEST_VERIFY_EQ(test_ptr, 205, b); + + wlmtk_element_get_pointer_area( + &parent_container.super_element, &l, &t, &r, &b); + BS_TEST_VERIFY_EQ(test_ptr, -21, l); + BS_TEST_VERIFY_EQ(test_ptr, -42, t); + BS_TEST_VERIFY_EQ(test_ptr, 113, r); + BS_TEST_VERIFY_EQ(test_ptr, 209, b); + + // There's nothing at (0, 0). + wlmtk_element_pointer_motion(&container.super_element, 0, 0, 7); + BS_TEST_VERIFY_FALSE(test_ptr, elem1_ptr->pointer_motion_called); + BS_TEST_VERIFY_FALSE(test_ptr, elem2_ptr->pointer_motion_called); + + wlmtk_element_pointer_motion(&parent_container.super_element, 0, 0, 7); + BS_TEST_VERIFY_FALSE(test_ptr, elem1_ptr->pointer_motion_called); + BS_TEST_VERIFY_FALSE(test_ptr, elem2_ptr->pointer_motion_called); + + // elem1 is at (-20, -40). + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(&container.super_element, -20, -40, 7)); + BS_TEST_VERIFY_TRUE(test_ptr, elem1_ptr->pointer_enter_called); + elem1_ptr->pointer_enter_called = false; + BS_TEST_VERIFY_TRUE(test_ptr, elem1_ptr->pointer_motion_called); + elem1_ptr->pointer_motion_called = false; + BS_TEST_VERIFY_FALSE(test_ptr, elem2_ptr->pointer_motion_called); + BS_TEST_VERIFY_EQ(test_ptr, 0, elem1_ptr->element.last_pointer_x); + BS_TEST_VERIFY_EQ(test_ptr, 0, elem1_ptr->element.last_pointer_y); + + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion( + &parent_container.super_element, -20, -40, 7)); + BS_TEST_VERIFY_FALSE(test_ptr, elem1_ptr->pointer_enter_called); + BS_TEST_VERIFY_TRUE(test_ptr, elem1_ptr->pointer_motion_called); + elem1_ptr->pointer_motion_called = false; + BS_TEST_VERIFY_FALSE(test_ptr, elem2_ptr->pointer_motion_called); + BS_TEST_VERIFY_EQ(test_ptr, 0, elem1_ptr->element.last_pointer_x); + BS_TEST_VERIFY_EQ(test_ptr, 0, elem1_ptr->element.last_pointer_y); + + // elem2 is covering the area at (107, 302). + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion( + &parent_container.super_element, 107, 203, 7)); + BS_TEST_VERIFY_TRUE(test_ptr, elem1_ptr->pointer_motion_called); + BS_TEST_VERIFY_TRUE(test_ptr, isnan(elem1_ptr->element.last_pointer_x)); + elem1_ptr->pointer_motion_called = false; + BS_TEST_VERIFY_TRUE(test_ptr, elem1_ptr->pointer_leave_called); + elem1_ptr->pointer_leave_called = false; + BS_TEST_VERIFY_TRUE(test_ptr, elem2_ptr->pointer_enter_called); + elem2_ptr->pointer_enter_called = false; + BS_TEST_VERIFY_TRUE(test_ptr, elem2_ptr->pointer_motion_called); + elem2_ptr->pointer_motion_called = false; + BS_TEST_VERIFY_EQ(test_ptr, 7, elem2_ptr->element.last_pointer_x); + BS_TEST_VERIFY_EQ(test_ptr, 3, elem2_ptr->element.last_pointer_y); + + // The pointer area of elem2 is covering the area at (112, 208). + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion( + &parent_container.super_element, 112, 208, 7)); + BS_TEST_VERIFY_FALSE(test_ptr, elem1_ptr->pointer_leave_called); + BS_TEST_VERIFY_FALSE(test_ptr, elem1_ptr->pointer_motion_called); + BS_TEST_VERIFY_TRUE(test_ptr, elem2_ptr->pointer_motion_called); + elem2_ptr->pointer_motion_called = false; + BS_TEST_VERIFY_EQ(test_ptr, 12, elem2_ptr->element.last_pointer_x); + BS_TEST_VERIFY_EQ(test_ptr, 8, elem2_ptr->element.last_pointer_y); + + // The pointer area of elem2 does not include (113, 209). + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_element_pointer_motion( + &parent_container.super_element, 113, 209, 7)); + BS_TEST_VERIFY_FALSE(test_ptr, elem1_ptr->pointer_motion_called); + BS_TEST_VERIFY_TRUE(test_ptr, elem2_ptr->pointer_motion_called); + BS_TEST_VERIFY_TRUE(test_ptr, isnan(elem2_ptr->element.last_pointer_x)); + BS_TEST_VERIFY_TRUE(test_ptr, elem2_ptr->pointer_leave_called); + elem2_ptr->pointer_leave_called = false; + + // All set. clean it up. + wlmtk_container_remove_element(&container, &elem1_ptr->element); + wlmtk_element_destroy(&elem1_ptr->element); + wlmtk_container_remove_element(&container, &elem2_ptr->element); + wlmtk_element_destroy(&elem2_ptr->element); + + wlmtk_container_remove_element(&parent_container, + &container.super_element); + wlmtk_container_fini(&parent_container); + wlmtk_container_fini(&container); +} + +/* ------------------------------------------------------------------------- */ +/** Tests that pointer focus is updated when elements are updated. */ +void test_pointer_focus(bs_test_t *test_ptr) +{ + wlmtk_container_t container; + BS_ASSERT(wlmtk_container_init(&container, NULL)); + + wlmtk_fake_element_t *elem1_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&elem1_ptr->element, true); + wlmtk_fake_element_t *elem2_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&elem2_ptr->element, true); + + // Case 1: An empty container, will not have a pointer-focussed element. + BS_TEST_VERIFY_EQ(test_ptr, NULL, container.pointer_focus_element_ptr); + + // Case 2: Adding a visible element at (0, 0): Focus remains NULL, since + // motion() was not called yet and we don't have known pointer position. + wlmtk_container_add_element(&container, &elem1_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, NULL, container.pointer_focus_element_ptr); + wlmtk_container_remove_element(&container, &elem1_ptr->element); + + // Case 3: Call motion() first, then add a visible element at (0, 0). Focus + // should switch there. + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_element_pointer_motion(&container.super_element, 0, 0, 7)); + wlmtk_container_add_element(&container, &elem1_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, + &elem1_ptr->element, + container.pointer_focus_element_ptr); + + // Case 4: Add another visible element. Focus changes, since on top. + wlmtk_container_add_element(&container, &elem2_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, + &elem2_ptr->element, + container.pointer_focus_element_ptr); + + // Case 5: Elem2 (added last = on top) becomes invisible. Focus changes. + wlmtk_element_set_visible(&elem2_ptr->element, false); + BS_TEST_VERIFY_EQ( + test_ptr, + &elem1_ptr->element, + container.pointer_focus_element_ptr); + + // Case 6: Elem1 becomes invisible. Focus changes to NULL. + wlmtk_element_set_visible(&elem1_ptr->element, false); + BS_TEST_VERIFY_EQ( + test_ptr, + NULL, + container.pointer_focus_element_ptr); + + // Case 7: Elem1 becomes visible. Focus changes to elem1 again. + wlmtk_element_set_visible(&elem1_ptr->element, true); + BS_TEST_VERIFY_EQ( + test_ptr, + &elem1_ptr->element, + container.pointer_focus_element_ptr); + + // Case 8: Remove Elem1. Focus changes to NULL. + wlmtk_container_remove_element(&container, &elem1_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, + NULL, + container.pointer_focus_element_ptr); + + // Case 9: Elem2 becomes visible, focus changes there. + wlmtk_element_set_visible(&elem2_ptr->element, true); + BS_TEST_VERIFY_EQ( + test_ptr, + &elem2_ptr->element, + container.pointer_focus_element_ptr); + + // Case 10: Elem2 is removed. Focus is now NULL, and leave() is called for + // the element that was removed. + elem2_ptr->pointer_leave_called = false; + wlmtk_container_remove_element(&container, &elem2_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, + NULL, + container.pointer_focus_element_ptr); + BS_TEST_VERIFY_TRUE( + test_ptr, + elem2_ptr->pointer_leave_called); + + wlmtk_element_destroy(&elem2_ptr->element); + wlmtk_element_destroy(&elem1_ptr->element); + wlmtk_container_fini(&container); +} + +/* ------------------------------------------------------------------------- */ +/** Tests that pointer focus is updated when elements are moved. */ +void test_pointer_focus_move(bs_test_t *test_ptr) +{ + wlmtk_container_t container; + BS_ASSERT(wlmtk_container_init(&container, NULL)); + + // Setup to span an area where the container catches pointer coordinates. + wlmtk_fake_element_t *elem1_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&elem1_ptr->element, true); + wlmtk_element_set_position(&elem1_ptr->element, -20, 0); + wlmtk_container_add_element(&container, &elem1_ptr->element); + wlmtk_fake_element_t *elem2_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&elem2_ptr->element, true); + wlmtk_element_set_position(&elem2_ptr->element, 20, 0); + wlmtk_container_add_element(&container, &elem2_ptr->element); + + // Need the container to pick up the cursor position. + wlmtk_element_pointer_motion(&container.super_element, 0, 0, 7); + + // Is off the cursor, will get focus. + BS_TEST_VERIFY_EQ( + test_ptr, + NULL, + container.pointer_focus_element_ptr); + + // Now moves below the cursor, will get focus. + wlmtk_element_set_position(&elem1_ptr->element, 0, 0); + BS_TEST_VERIFY_EQ( + test_ptr, + &elem1_ptr->element, + container.pointer_focus_element_ptr); + + wlmtk_container_remove_element(&container, &elem2_ptr->element); + wlmtk_container_remove_element(&container, &elem1_ptr->element); + + wlmtk_element_destroy(&elem2_ptr->element); + wlmtk_element_destroy(&elem1_ptr->element); + wlmtk_container_fini(&container); +} + + +/* ------------------------------------------------------------------------- */ +/** Tests that pointer focus is updated across layers of containers. */ +void test_pointer_focus_layered(bs_test_t *test_ptr) +{ + wlmtk_container_t container1; + BS_ASSERT(wlmtk_container_init(&container1, NULL)); + wlmtk_container_t container2; + BS_ASSERT(wlmtk_container_init(&container2, NULL)); + wlmtk_element_set_visible(&container2.super_element, true); + + wlmtk_fake_element_t *elem1_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&elem1_ptr->element, true); + wlmtk_fake_element_t *elem2_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&elem2_ptr->element, true); + + // Prepare: Motion was called, will not have any focus. + wlmtk_element_pointer_motion(&container1.super_element, 0, 0, 7); + BS_TEST_VERIFY_EQ(test_ptr, NULL, container1.pointer_focus_element_ptr); + + // Case 1: Add element 2 to second container, then add this container. + // this must re-trigger focus and pass it to elem2. + wlmtk_container_add_element(&container2, &elem2_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, + NULL, + container1.pointer_focus_element_ptr); + wlmtk_container_add_element(&container1, &container2.super_element); + BS_TEST_VERIFY_EQ( + test_ptr, + &container2.super_element, + container1.pointer_focus_element_ptr); + BS_TEST_VERIFY_EQ( + test_ptr, + &elem2_ptr->element, container2.pointer_focus_element_ptr); + + // Case 2: Add elem1 to container1. Must change focus there, and call + // leave for container2 and elem2. + elem2_ptr->pointer_leave_called = false; + wlmtk_container_add_element(&container1, &elem1_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, + &elem1_ptr->element, + container1.pointer_focus_element_ptr); + BS_TEST_VERIFY_TRUE(test_ptr, elem2_ptr->pointer_leave_called); + + // Case 3: Bring container2 to top. Now elem2 has focus. + elem1_ptr->pointer_leave_called = false; + wlmtk_container_remove_element(&container1, &container2.super_element); + wlmtk_container_add_element(&container1, &container2.super_element); + BS_TEST_VERIFY_EQ( + test_ptr, + &container2.super_element, + container1.pointer_focus_element_ptr); + BS_TEST_VERIFY_EQ( + test_ptr, + &elem2_ptr->element, + container2.pointer_focus_element_ptr); + BS_TEST_VERIFY_TRUE(test_ptr, elem1_ptr->pointer_leave_called); + + // Case 4: Remove elem2, drop focus back to elem1. + elem2_ptr->pointer_leave_called = false; + wlmtk_container_remove_element(&container2, &elem2_ptr->element); + BS_TEST_VERIFY_EQ( + test_ptr, + &elem1_ptr->element, + container1.pointer_focus_element_ptr); + BS_TEST_VERIFY_TRUE(test_ptr, elem2_ptr->pointer_leave_called); + + wlmtk_container_remove_element(&container1, &elem1_ptr->element); + wlmtk_element_destroy(&elem2_ptr->element); + wlmtk_element_destroy(&elem1_ptr->element); + + wlmtk_container_remove_element(&container1, &container2.super_element); + wlmtk_container_fini(&container2); + wlmtk_container_fini(&container1); +} + +/* ------------------------------------------------------------------------- */ +/** Tests that pointer DOWN is forwarded to element with pointer focus. */ +void test_pointer_button(bs_test_t *test_ptr) +{ + wlmtk_container_t container; + BS_ASSERT(wlmtk_container_init(&container, NULL)); + + wlmtk_fake_element_t *elem1_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&elem1_ptr->element, true); + elem1_ptr->dimensions.width = 1; + elem1_ptr->dimensions.height = 1; + wlmtk_container_add_element(&container, &elem1_ptr->element); + + wlmtk_fake_element_t *elem2_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_position(&elem2_ptr->element, 10, 10); + wlmtk_element_set_visible(&elem2_ptr->element, true); + wlmtk_container_add_element_atop(&container, NULL, &elem2_ptr->element); + + wlmtk_button_event_t button = { + .button = BTN_LEFT, .type = WLMTK_BUTTON_DOWN + }; + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_element_pointer_button(&container.super_element, &button)); + + // DOWN events go to the focussed element. + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(&container.super_element, 0, 0, 7)); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(&container.super_element, &button)); + BS_TEST_VERIFY_EQ( + test_ptr, + &elem1_ptr->element, + container.left_button_element_ptr); + BS_TEST_VERIFY_TRUE( + test_ptr, elem1_ptr->pointer_button_called); + + // Moves, pointer focus is now on elem2. + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(&container.super_element, 10, 10, 7)); + BS_TEST_VERIFY_EQ( + test_ptr, + &elem2_ptr->element, + container.pointer_focus_element_ptr); + + // The UP event is still received by elem1. + elem1_ptr->pointer_button_called = false; + button.type = WLMTK_BUTTON_UP; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(&container.super_element, &button)); + BS_TEST_VERIFY_TRUE( + test_ptr, elem1_ptr->pointer_button_called); + + // Click will be ignored + button.type = WLMTK_BUTTON_CLICK; + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_element_pointer_button(&container.super_element, &button)); + + // New DOWN event goes to elem2, though. + elem2_ptr->pointer_button_called = false; + button.type = WLMTK_BUTTON_DOWN; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(&container.super_element, &button)); + BS_TEST_VERIFY_TRUE( + test_ptr, elem2_ptr->pointer_button_called); + + // And UP event now goes to elem2. + elem2_ptr->pointer_button_called = false; + button.type = WLMTK_BUTTON_UP; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(&container.super_element, &button)); + BS_TEST_VERIFY_TRUE( + test_ptr, elem2_ptr->pointer_button_called); + + // Here, CLICK goes to elem2. + elem2_ptr->pointer_button_called = false; + button.type = WLMTK_BUTTON_CLICK; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(&container.super_element, &button)); + BS_TEST_VERIFY_TRUE( + test_ptr, elem2_ptr->pointer_button_called); + + // After removing, further UP events won't be accidentally sent there. + wlmtk_container_remove_element(&container, &elem1_ptr->element); + wlmtk_container_remove_element(&container, &elem2_ptr->element); + button.type = WLMTK_BUTTON_UP; + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_element_pointer_button(&container.super_element, &button)); + BS_TEST_VERIFY_EQ( + test_ptr, + NULL, + container.left_button_element_ptr); + wlmtk_element_destroy(&elem2_ptr->element); + wlmtk_element_destroy(&elem1_ptr->element); + wlmtk_container_fini(&container); +} + + +/* == End of container.c =================================================== */ diff --git a/src/toolkit/container.h b/src/toolkit/container.h new file mode 100644 index 00000000..b5b4cb5e --- /dev/null +++ b/src/toolkit/container.h @@ -0,0 +1,224 @@ +/* ========================================================================= */ +/** + * @file container.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_CONTAINER_H__ +#define __WLMTK_CONTAINER_H__ + +#include +#include + +/** Forward declaration: Container. */ +typedef struct _wlmtk_container_t wlmtk_container_t; +/** Forward declaration: Container virtual method table. */ +typedef struct _wlmtk_container_vmt_t wlmtk_container_vmt_t; + +#include "element.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Virtual method table of the container. */ +struct _wlmtk_container_vmt_t { + /** + * Updates the layout of the container elements. + * + * Will be called by this, when an element is added or removed. + * Additionally, this should be invoked by contained elements when + * the visibility or dimensions change. + * + * Each container will propagate a + * @ref wlmtk_container_vmt_t::update_layout call + * upwards to it's parent container. The root container will then trigger + * an update to pointer focus (since by then, the layout is updated). + */ + void (*update_layout)(wlmtk_container_t *container_ptr); +}; + +/** State of the container. */ +struct _wlmtk_container_t { + /** Super class of the container. */ + wlmtk_element_t super_element; + /** Virtual method table of the super element before extending it. */ + wlmtk_element_vmt_t orig_super_element_vmt; + + /** Virtual method table for the container. */ + wlmtk_container_vmt_t vmt; + + /** + * Elements contained here. + * + * `head_ptr` is the topmost element, and `tail_ptr` the bottom-most one. + */ + bs_dllist_t elements; + + /** Scene tree. */ + struct wlr_scene_tree *wlr_scene_tree_ptr; + + /** Listener for the `destroy` signal of `wlr_scene_tree_ptr->node`. */ + struct wl_listener wlr_scene_tree_node_destroy_listener; + + /** Stores the element with current pointer focus. May be NULL. */ + wlmtk_element_t *pointer_focus_element_ptr; + /** Stores the element which received WLMTK_BUTTON_DOWN for BTN_LEFT. */ + wlmtk_element_t *left_button_element_ptr; +}; + +/** + * Initializes the container with the provided virtual method table. + * + * @param container_ptr + * @param env_ptr + * + * @return true on success. + */ +bool wlmtk_container_init(wlmtk_container_t *container_ptr, + wlmtk_env_t *env_ptr); + +/** + * Extends the container's virtual methods. + * + * @param container_ptr + * @param container_vmt_ptr + * + * @return The previous virtual method table. + */ +wlmtk_container_vmt_t wlmtk_container_extend( + wlmtk_container_t *container_ptr, + const wlmtk_container_vmt_t *container_vmt_ptr); + +/** + * Initializes the container, and attach to WLR sene graph. + * + * @param container_ptr + * @param env_ptr + * @param root_wlr_scene_tree_ptr + * + * @return true on success. + */ +bool wlmtk_container_init_attached( + wlmtk_container_t *container_ptr, + wlmtk_env_t *env_ptr, + struct wlr_scene_tree *root_wlr_scene_tree_ptr); + +/** + * Un-initializes the container. + * + * Any element still in `elements` will be destroyed. + * + * @param container_ptr + */ +void wlmtk_container_fini( + wlmtk_container_t *container_ptr); + +/** + * Adds `element_ptr` to the container. + * + * Requires that `element_ptr` is not added to a container yet. The element + * will be added at the top of the container. + * + * @param container_ptr + * @param element_ptr + */ +void wlmtk_container_add_element( + wlmtk_container_t *container_ptr, + wlmtk_element_t *element_ptr); + +/** + * Adds `element_ptr` to the container atop the reference's position. + * + * If reference_element_ptr is NULL, the element will be added at the back. + * + * @param container_ptr + * @param reference_element_ptr Must be an element of this container. + * @param element_ptr + */ +void wlmtk_container_add_element_atop( + wlmtk_container_t *container_ptr, + wlmtk_element_t *reference_element_ptr, + wlmtk_element_t *element_ptr); + +/** + * Removes `element_ptr` from the container. + * + * Expects that `container_ptr` is `element_ptr`'s parent container. + * + * @param container_ptr + * @param element_ptr + */ +void wlmtk_container_remove_element( + wlmtk_container_t *container_ptr, + wlmtk_element_t *element_ptr); + +/** + * Places `element_ptr` at the top (head) of the container. + * + * Expects that `container_ptr` is `element_ptr`'s parent container. + * + * @param container_ptr + * @param element_ptr + */ +void wlmtk_container_raise_element_to_top( + wlmtk_container_t *container_ptr, + wlmtk_element_t *element_ptr); + +/** + * Updates pointer focus of the container. + * + * @param container_ptr + */ +void wlmtk_container_update_pointer_focus(wlmtk_container_t *container_ptr); + +/** + * Updates the layout of the container. + * + * @param container_ptr Container to update. NULL implies a no-op. + */ +static inline void wlmtk_container_update_layout( + wlmtk_container_t *container_ptr) +{ + container_ptr->vmt.update_layout(container_ptr); +} + +/** + * Returns the wlroots scene graph tree for this node. + * + * Private: Should be called only by wlmtk_element_t. + * + * @param container_ptr + * + * @return The scene tree, or NULL if not attached to a scene graph. + */ +struct wlr_scene_tree *wlmtk_container_wlr_scene_tree( + wlmtk_container_t *container_ptr); + +/** Unit tests for the container. */ +extern const bs_test_case_t wlmtk_container_test_cases[]; + +/** Constructor for a fake container with a scene tree. */ +wlmtk_container_t *wlmtk_container_create_fake_parent(void); +/** Destructor for that fake container. */ +void wlmtk_container_destroy_fake_parent(wlmtk_container_t *container_ptr); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_CONTAINER_H__ */ +/* == End of container.h =================================================== */ diff --git a/src/toolkit/content.c b/src/toolkit/content.c new file mode 100644 index 00000000..84bb034b --- /dev/null +++ b/src/toolkit/content.c @@ -0,0 +1,205 @@ +/* ========================================================================= */ +/** + * @file content.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "content.h" + +#include "surface.h" + +/* == Declarations ========================================================= */ + +/* == Data ================================================================= */ + +void *wlmtk_content_identifier_ptr = wlmtk_content_init; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +bool wlmtk_content_init( + wlmtk_content_t *content_ptr, + wlmtk_surface_t *surface_ptr, + wlmtk_env_t *env_ptr) +{ + BS_ASSERT(NULL != content_ptr); + memset(content_ptr, 0, sizeof(wlmtk_content_t)); + + if (!wlmtk_container_init(&content_ptr->super_container, env_ptr)) { + return false; + } + + BS_ASSERT(NULL != surface_ptr); + wlmtk_container_add_element( + &content_ptr->super_container, + wlmtk_surface_element(surface_ptr)); + content_ptr->surface_ptr = surface_ptr; + content_ptr->identifier_ptr = wlmtk_content_identifier_ptr; + + wlmtk_element_set_visible(wlmtk_surface_element(surface_ptr), true); + return true; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_content_fini( + wlmtk_content_t *content_ptr) +{ + if (NULL != content_ptr->surface_ptr) { + wlmtk_container_remove_element( + &content_ptr->super_container, + wlmtk_surface_element(content_ptr->surface_ptr)); + content_ptr->surface_ptr = NULL; + } + memset(content_ptr, 0, sizeof(wlmtk_content_t)); +} + +/* ------------------------------------------------------------------------- */ +wlmtk_content_vmt_t wlmtk_content_extend( + wlmtk_content_t *content_ptr, + const wlmtk_content_vmt_t *content_vmt_ptr) +{ + wlmtk_content_vmt_t orig_vmt = content_ptr->vmt; + + if (NULL != content_vmt_ptr->request_maximized) { + content_ptr->vmt.request_maximized = + content_vmt_ptr->request_maximized; + } + if (NULL != content_vmt_ptr->request_fullscreen) { + content_ptr->vmt.request_fullscreen = + content_vmt_ptr->request_fullscreen; + } + + return orig_vmt; +} + +/* ------------------------------------------------------------------------- */ +uint32_t wlmtk_content_request_size( + wlmtk_content_t *content_ptr, + int width, + int height) +{ + return wlmtk_surface_request_size(content_ptr->surface_ptr, width, height); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_content_request_close(wlmtk_content_t *content_ptr) +{ + wlmtk_surface_request_close(content_ptr->surface_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_content_set_activated( + wlmtk_content_t *content_ptr, + bool activated) +{ + wlmtk_surface_set_activated(content_ptr->surface_ptr, activated); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_content_get_size( + wlmtk_content_t *content_ptr, + int *width_ptr, + int *height_ptr) +{ + wlmtk_surface_get_size(content_ptr->surface_ptr, width_ptr, height_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_content_commit_size( + wlmtk_content_t *content_ptr, + uint32_t serial, + int width, + int height) +{ + wlmtk_surface_commit_size(content_ptr->surface_ptr, serial, width, height); + + if (NULL != content_ptr->window_ptr) { + wlmtk_window_serial(content_ptr->window_ptr, serial); + } +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_content_set_window( + wlmtk_content_t *content_ptr, + wlmtk_window_t *window_ptr) +{ + content_ptr->window_ptr = window_ptr; +} + +/* ------------------------------------------------------------------------- */ +wlmtk_element_t *wlmtk_content_element(wlmtk_content_t *content_ptr) +{ + return &content_ptr->super_container.super_element; +} + +/* == Local (static) methods =============================================== */ + +/* == Unit tests =========================================================== */ + +static void test_init_fini(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_content_test_cases[] = { + { 1, "init_fini", test_init_fini }, + { 0, NULL, NULL } +}; + +/* ------------------------------------------------------------------------- */ +/** Tests setup and teardown. */ +void test_init_fini(bs_test_t *test_ptr) +{ + wlmtk_content_t content; + struct wlr_box box; + + wlmtk_fake_surface_t *fs_ptr = wlmtk_fake_surface_create(); + + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_content_init(&content, &fs_ptr->surface, NULL)); + wlmtk_element_t *element_ptr = wlmtk_content_element(&content); + + // Initial size is zero. + box = wlmtk_element_get_dimensions_box(element_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 0, box.x); + BS_TEST_VERIFY_EQ(test_ptr, 0, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 0, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 0, box.height); + + // Pointer motion, should report to not be within the content. + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_element_pointer_motion(element_ptr, 10, 10, 0)); + + // Request & commit a sensible size, verifies the content reports it. + wlmtk_surface_request_size(&fs_ptr->surface, 200, 100); + wlmtk_fake_surface_commit(fs_ptr); + box = wlmtk_element_get_dimensions_box(element_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 0, box.x); + BS_TEST_VERIFY_EQ(test_ptr, 0, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 200, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 100, box.height); + + // Pointer motion shouuld now report to be within the content. + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(element_ptr, 10, 10, 0)); + + wlmtk_content_fini(&content); + wlmtk_fake_surface_destroy(fs_ptr); + +} + +/* == End of content.c ===================================================== */ diff --git a/src/toolkit/content.h b/src/toolkit/content.h new file mode 100644 index 00000000..fc617f12 --- /dev/null +++ b/src/toolkit/content.h @@ -0,0 +1,196 @@ +/* ========================================================================= */ +/** + * @file content.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_CONTENT_H__ +#define __WLMTK_CONTENT_H__ + + +/** Forward declaration: Content state. */ +typedef struct _wlmtk_content_t wlmtk_content_t; +/** Forward declaration: Content virtual method table. */ +typedef struct _wlmtk_content_vmt_t wlmtk_content_vmt_t; +/** Forward declaration: Window. */ +typedef struct _wlmtk_window_t wlmtk_window_t +;/** Forward declaration: State of a toolkit's WLR surface. */ +typedef struct _wlmtk_surface_t wlmtk_surface_t; + +#include "container.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Virtual method table of @ref wlmtk_content_t. */ +struct _wlmtk_content_vmt_t { + /** + * Requests the content to be set to maximized mode. + * + * Once the content has changed to `maximized` mode (potentially an + * asynchronous operation), @ref wlmtk_window_commit_maximized ought to be + * called, if the content belongs to a window. + * + * @param content_ptr + * @param maximized + * + * @return XDG toplevel configuration serial. + */ + uint32_t (*request_maximized)(wlmtk_content_t *content_ptr, + bool maximized); + + /** + * Requests the content to be set to fullscreen mode. + * + * Some contents may adjust the decoration suitably. Once the content has + * changed to fullscreen mode (potentially an asynchronous operation), + * @ref wlmtk_window_commit_fullscreen ought to be called, if the content + * belongs to a window. + * + * @param content_ptr + * @param fullscreen + * + * @return XDG toplevel configuration serial. + */ + uint32_t (*request_fullscreen)(wlmtk_content_t *content_ptr, + bool fullscreen); +}; + +/** State of window content. */ +struct _wlmtk_content_t { + /** Temporary: Identifier, to disambiguate from XDG nodes. */ + void *identifier_ptr; + + /** Super class of the content: A container, holding surface & popups. */ + wlmtk_container_t super_container; + /** Virtual method table of the content. */ + wlmtk_content_vmt_t vmt; + + /** The principal surface of the content. */ + wlmtk_surface_t *surface_ptr; + /** The window this content belongs to. Set when creating the window. */ + wlmtk_window_t *window_ptr; +}; + +/** + * Identifying pointer: Value unique to wlmtk_content. + * + * TODO(kaeser@gubbe.ch): Remove, once migrated to toolkit. + */ +extern void *wlmtk_content_identifier_ptr; + +/** + * Initializes the content with the given surface. + * + * @param content_ptr + * @param surface_ptr + * @param env_ptr + * + * @return true on success. + */ +bool wlmtk_content_init( + wlmtk_content_t *content_ptr, + wlmtk_surface_t *surface_ptr, + wlmtk_env_t *env_ptr); + +/** + * Un-initializes the content. + * + * @param content_ptr + */ +void wlmtk_content_fini( + wlmtk_content_t *content_ptr); + +/** + * Extends the content by specifying virtual methods. + * + * @param content_ptr + * @param content_vmt_ptr + * + * @return The original virtual method table. + */ +wlmtk_content_vmt_t wlmtk_content_extend( + wlmtk_content_t *content_ptr, + const wlmtk_content_vmt_t *content_vmt_ptr); + +/** Requests size: Forwards to @ref wlmtk_surface_request_size. */ +uint32_t wlmtk_content_request_size( + wlmtk_content_t *content_ptr, + int width, + int height); + +/** Requests maximized. See @ref wlmtk_content_vmt_t::request_maximized. */ +static inline uint32_t wlmtk_content_request_maximized( + wlmtk_content_t *content_ptr, + bool maximized) { + if (NULL == content_ptr->vmt.request_maximized) return 0; + return content_ptr->vmt.request_maximized(content_ptr, maximized); +} + +/** Requests fullscreen. See @ref wlmtk_content_vmt_t::request_fullscreen. */ +static inline uint32_t wlmtk_content_request_fullscreen( + wlmtk_content_t *content_ptr, + bool fullscreen) { + if (NULL == content_ptr->vmt.request_fullscreen) return 0; + return content_ptr->vmt.request_fullscreen(content_ptr, fullscreen); +} + +/** + * Sets the window for the content. + * + * Private: Should only be called by Window ctor (a friend). + * + * @param content_ptr + * @param window_ptr + */ +void wlmtk_content_set_window( + wlmtk_content_t *content_ptr, + wlmtk_window_t *window_ptr); + +/** Requests close: Forwards to @ref wlmtk_surface_request_close. */ +void wlmtk_content_request_close(wlmtk_content_t *content_ptr); + +/** Set activated: Forwards to @ref wlmtk_surface_set_activated. */ +void wlmtk_content_set_activated( + wlmtk_content_t *content_ptr, + bool activated); + +/** Gets size: Forwards to @ref wlmtk_surface_get_size. */ +void wlmtk_content_get_size( + wlmtk_content_t *content_ptr, + int *width_ptr, + int *height_ptr); + +/** Commits size: Forwards to @ref wlmtk_surface_commit_size. */ +void wlmtk_content_commit_size( + wlmtk_content_t *content_ptr, + uint32_t serial, + int width, + int height); + +/** Returns the superclass' instance of @ref wlmtk_element_t. */ +wlmtk_element_t *wlmtk_content_element(wlmtk_content_t *content_ptr); + +/** Content's unit tests. */ +extern const bs_test_case_t wlmtk_content_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_CONTENT_H__ */ +/* == End of content.h ===================================================== */ diff --git a/src/toolkit/element.c b/src/toolkit/element.c new file mode 100644 index 00000000..5a2ff4db --- /dev/null +++ b/src/toolkit/element.c @@ -0,0 +1,759 @@ +/* ========================================================================= */ +/** + * @file element.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "element.h" +#include "container.h" + +#include "util.h" + +#define WLR_USE_UNSTABLE +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +static void _wlmtk_element_get_pointer_area( + wlmtk_element_t *element_ptr, + int *x1_ptr, + int *y1_ptr, + int *x2_ptr, + int *y2_ptr); +static bool _wlmtk_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, + double y, + uint32_t time_msec); +static bool _wlmtk_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr); +static void _wlmtk_element_pointer_enter( + wlmtk_element_t *element_ptr); +static void _wlmtk_element_pointer_leave( + wlmtk_element_t *element_ptr); + +static void handle_wlr_scene_node_destroy( + struct wl_listener *listener_ptr, + void *data_ptr); + +/* == Data ================================================================= */ + +/** Default virtual method table. Initializes the non-abstract methods. */ +static const wlmtk_element_vmt_t element_vmt = { + .get_pointer_area = _wlmtk_element_get_pointer_area, + .pointer_motion = _wlmtk_element_pointer_motion, + .pointer_button = _wlmtk_element_pointer_button, + .pointer_enter = _wlmtk_element_pointer_enter, + .pointer_leave = _wlmtk_element_pointer_leave, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +bool wlmtk_element_init( + wlmtk_element_t *element_ptr, + wlmtk_env_t *env_ptr) +{ + BS_ASSERT(NULL != element_ptr); + memset(element_ptr, 0, sizeof(wlmtk_element_t)); + element_ptr->vmt = element_vmt; + element_ptr->env_ptr = env_ptr; + + element_ptr->last_pointer_x = NAN; + element_ptr->last_pointer_y = NAN; + element_ptr->last_pointer_time_msec = 0; + return true; +} + +/* ------------------------------------------------------------------------- */ +wlmtk_element_vmt_t wlmtk_element_extend( + wlmtk_element_t *element_ptr, + const wlmtk_element_vmt_t *element_vmt_ptr) +{ + wlmtk_element_vmt_t orig_vmt = element_ptr->vmt; + + // Only overwrite provided methods. + if (NULL != element_vmt_ptr->destroy) { + element_ptr->vmt.destroy = element_vmt_ptr->destroy; + } + if (NULL != element_vmt_ptr->create_scene_node) { + element_ptr->vmt.create_scene_node = element_vmt_ptr->create_scene_node; + } + if (NULL != element_vmt_ptr->get_dimensions) { + element_ptr->vmt.get_dimensions = element_vmt_ptr->get_dimensions; + } + if (NULL != element_vmt_ptr->get_pointer_area) { + element_ptr->vmt.get_pointer_area = element_vmt_ptr->get_pointer_area; + } + if (NULL != element_vmt_ptr->pointer_motion) { + element_ptr->vmt.pointer_motion = element_vmt_ptr->pointer_motion; + } + if (NULL != element_vmt_ptr->pointer_button) { + element_ptr->vmt.pointer_button = element_vmt_ptr->pointer_button; + } + if (NULL != element_vmt_ptr->pointer_enter) { + element_ptr->vmt.pointer_enter = element_vmt_ptr->pointer_enter; + } + if (NULL != element_vmt_ptr->pointer_leave) { + element_ptr->vmt.pointer_leave = element_vmt_ptr->pointer_leave; + } + + return orig_vmt; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_element_fini( + wlmtk_element_t *element_ptr) +{ + // Verify we're no longer part of the scene graph, nor part of a container. + BS_ASSERT(NULL == element_ptr->wlr_scene_node_ptr); + BS_ASSERT(NULL == element_ptr->parent_container_ptr); + + memset(element_ptr, 0, sizeof(wlmtk_element_t)); +} + +/* ------------------------------------------------------------------------- */ +bs_dllist_node_t *wlmtk_dlnode_from_element( + wlmtk_element_t *element_ptr) +{ + return &element_ptr->dlnode; +} + +/* ------------------------------------------------------------------------- */ +wlmtk_element_t *wlmtk_element_from_dlnode( + bs_dllist_node_t *dlnode_ptr) +{ + return BS_CONTAINER_OF(dlnode_ptr, wlmtk_element_t, dlnode); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_element_set_parent_container( + wlmtk_element_t *element_ptr, + wlmtk_container_t *parent_container_ptr) +{ + if (element_ptr->parent_container_ptr == parent_container_ptr) return; + element_ptr->parent_container_ptr = parent_container_ptr; + wlmtk_element_attach_to_scene_graph(element_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_element_attach_to_scene_graph( + wlmtk_element_t *element_ptr) +{ + struct wlr_scene_tree *parent_wlr_scene_tree_ptr = NULL; + if (NULL != element_ptr->parent_container_ptr) { + parent_wlr_scene_tree_ptr = wlmtk_container_wlr_scene_tree( + element_ptr->parent_container_ptr); + } + + if (NULL == parent_wlr_scene_tree_ptr) { + if (NULL != element_ptr->wlr_scene_node_ptr) { + wl_list_remove(&element_ptr->wlr_scene_node_destroy_listener.link); + wlr_scene_node_destroy(element_ptr->wlr_scene_node_ptr); + element_ptr->wlr_scene_node_ptr = NULL; + } + return; + } + + if (NULL == element_ptr->wlr_scene_node_ptr) { + element_ptr->wlr_scene_node_ptr = element_ptr->vmt.create_scene_node( + element_ptr, parent_wlr_scene_tree_ptr); + wlmtk_util_connect_listener_signal( + &element_ptr->wlr_scene_node_ptr->events.destroy, + &element_ptr->wlr_scene_node_destroy_listener, + handle_wlr_scene_node_destroy); + wlr_scene_node_set_enabled(element_ptr->wlr_scene_node_ptr, + element_ptr->visible); + wlr_scene_node_set_position(element_ptr->wlr_scene_node_ptr, + element_ptr->x, + element_ptr->y); + return; + } + + BS_ASSERT(NULL != element_ptr->wlr_scene_node_ptr); + if (element_ptr->wlr_scene_node_ptr->parent == parent_wlr_scene_tree_ptr) { + // Parent does not change, nothing to do. + return; + } + + wlr_scene_node_reparent(element_ptr->wlr_scene_node_ptr, + parent_wlr_scene_tree_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_element_set_visible(wlmtk_element_t *element_ptr, bool visible) +{ + // Nothing to do? + if (element_ptr->visible == visible) return; + + element_ptr->visible = visible; + if (NULL != element_ptr->wlr_scene_node_ptr) { + wlr_scene_node_set_enabled(element_ptr->wlr_scene_node_ptr, visible); + } + + if (NULL != element_ptr->parent_container_ptr) { + wlmtk_container_update_layout(element_ptr->parent_container_ptr); + } +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_element_get_position( + wlmtk_element_t *element_ptr, + int *x_ptr, + int *y_ptr) +{ + // The node may have been moved without us noticing... update it. + if (NULL != element_ptr->wlr_scene_node_ptr) { + element_ptr->x = element_ptr->wlr_scene_node_ptr->x; + element_ptr->y = element_ptr->wlr_scene_node_ptr->y; + } + + if (NULL != x_ptr) *x_ptr = element_ptr->x; + if (NULL != y_ptr) *y_ptr = element_ptr->y; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_element_set_position( + wlmtk_element_t *element_ptr, + int x, + int y) +{ + if (NULL != element_ptr->wlr_scene_node_ptr) { + wlr_scene_node_set_position(element_ptr->wlr_scene_node_ptr, x, y); + } + + // Optimization clause: Can leave here, if coordinates didn't change. + if (element_ptr->x == x && element_ptr->y == y) return; + element_ptr->x = x; + element_ptr->y = y; + + if (NULL != element_ptr->parent_container_ptr) { + wlmtk_container_update_pointer_focus( + element_ptr->parent_container_ptr); + } +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, + double y, + uint32_t time_msec) +{ + bool within = element_ptr->vmt.pointer_motion(element_ptr, x, y, time_msec); + if (within == element_ptr->pointer_inside) return within; + + if (within) { + element_ptr->pointer_inside = true; + element_ptr->vmt.pointer_enter(element_ptr); + } else { + element_ptr->pointer_inside = false; + element_ptr->vmt.pointer_leave(element_ptr); + } + + return within; +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** Wraps to wlmtk_element_vmt_t::get_dimensions as default implementation. */ +void _wlmtk_element_get_pointer_area( + wlmtk_element_t *element_ptr, + int *x1_ptr, + int *y1_ptr, + int *x2_ptr, + int *y2_ptr) +{ + wlmtk_element_get_dimensions(element_ptr, x1_ptr, y1_ptr, x2_ptr, y2_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Stores pointer coordinates and timestamp. Returns true is x,y not NAN. */ +bool _wlmtk_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, + double y, + uint32_t time_msec) +{ + if (isnan(x) || isnan(y)) { + x = NAN; + y = NAN; + } + + element_ptr->last_pointer_x = x; + element_ptr->last_pointer_y = y; + element_ptr->last_pointer_time_msec = time_msec; + + return !isnan(x) && !isnan(y); +} + +/* ------------------------------------------------------------------------- */ +/** Does nothing, returns false. */ +bool _wlmtk_element_pointer_button( + __UNUSED__ wlmtk_element_t *element_ptr, + __UNUSED__ const wlmtk_button_event_t *button_event_ptr) +{ + return false; +} + +/* ------------------------------------------------------------------------- */ +/** Handler for when the pointer enters the area. Sets default cursor. */ +void _wlmtk_element_pointer_enter(wlmtk_element_t *element_ptr) +{ + // TODO(kaeser@gubbe.ch): Consider forking this out into a 'leaf element' + // class. 'enter' and 'leave' don't make that much sense for eg. a + // container. + wlmtk_env_set_cursor(element_ptr->env_ptr, WLMTK_CURSOR_DEFAULT); +} + +/* ------------------------------------------------------------------------- */ +/** Handler for when the pointer leaves the area. Nothing for default impl. */ +void _wlmtk_element_pointer_leave(__UNUSED__ wlmtk_element_t *element_ptr) +{ + // Nothing. +} + +/* ------------------------------------------------------------------------- */ +/** + * Handles the 'destroy' callback of the wlr_scene_node. + * + * A call here indicates that teardown was not executed properly! + * + * @param listener_ptr + * @param data_ptr + */ +void handle_wlr_scene_node_destroy( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_element_t *element_ptr = BS_CONTAINER_OF( + listener_ptr, wlmtk_element_t, wlr_scene_node_destroy_listener); + bs_log(BS_FATAL, "Unexpected call into node's dtor on %p!", element_ptr); +} + +/* == Fake element, useful for unit tests. ================================= */ + +static void fake_destroy(wlmtk_element_t *element_ptr); +static struct wlr_scene_node *fake_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr); +static void fake_get_dimensions( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr); +static void fake_get_pointer_area( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr); +static bool fake_pointer_motion( + wlmtk_element_t *element_ptr, + double x, double y, + uint32_t time_msec); +static bool fake_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr); +static void fake_pointer_enter( + wlmtk_element_t *element_ptr); +static void fake_pointer_leave( + wlmtk_element_t *element_ptr); + +/** Virtual method table for the fake element. */ +static const wlmtk_element_vmt_t fake_element_vmt = { + .destroy = fake_destroy, + .create_scene_node = fake_create_scene_node, + .get_dimensions = fake_get_dimensions, + .get_pointer_area = fake_get_pointer_area, + .pointer_motion = fake_pointer_motion, + .pointer_button = fake_pointer_button, + .pointer_enter = fake_pointer_enter, + .pointer_leave = fake_pointer_leave, +}; + +/* ------------------------------------------------------------------------- */ +wlmtk_fake_element_t *wlmtk_fake_element_create(void) +{ + wlmtk_fake_element_t *fake_element_ptr = logged_calloc( + 1, sizeof(wlmtk_fake_element_t)); + if (NULL == fake_element_ptr) return NULL; + + if (!wlmtk_element_init(&fake_element_ptr->element, NULL)) { + fake_destroy(&fake_element_ptr->element); + return NULL; + } + + fake_element_ptr->orig_vmt = wlmtk_element_extend( + &fake_element_ptr->element, &fake_element_vmt); + + return fake_element_ptr; +} + +/* ------------------------------------------------------------------------- */ +/** dtor for the "fake" element used for tests. */ +void fake_destroy(wlmtk_element_t *element_ptr) +{ + wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_fake_element_t, element); + wlmtk_element_fini(&fake_element_ptr->element); + free(fake_element_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** A "fake" 'create_scene_node': Creates a non-attached buffer node. */ +struct wlr_scene_node *fake_create_scene_node( + __UNUSED__ wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr) +{ + struct wlr_scene_buffer *wlr_scene_buffer_ptr = wlr_scene_buffer_create( + wlr_scene_tree_ptr, NULL); + return &wlr_scene_buffer_ptr->node; +} + +/* ------------------------------------------------------------------------- */ +/** A "fake" 'get_dimensions'. */ +void fake_get_dimensions( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr) +{ + wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_fake_element_t, element); + if (NULL != left_ptr) *left_ptr = fake_element_ptr->dimensions.x; + if (NULL != top_ptr) *top_ptr = fake_element_ptr->dimensions.y; + if (NULL != right_ptr) *right_ptr = ( + fake_element_ptr->dimensions.width - fake_element_ptr->dimensions.x); + if (NULL != bottom_ptr) *bottom_ptr = ( + fake_element_ptr->dimensions.height - fake_element_ptr->dimensions.y); +} + +/* ------------------------------------------------------------------------- */ +/** A "fake" 'get_pointer_area'. */ +void fake_get_pointer_area( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr) +{ + wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_fake_element_t, element); + if (NULL != left_ptr) *left_ptr = -1; + if (NULL != top_ptr) *top_ptr = -2; + if (NULL != right_ptr) *right_ptr = ( + fake_element_ptr->dimensions.width + 3); + if (NULL != bottom_ptr) *bottom_ptr = ( + fake_element_ptr->dimensions.height + 4); +} + +/* ------------------------------------------------------------------------- */ +/** Handles 'motion' events for the fake element, updates last position. */ +bool fake_pointer_motion( + wlmtk_element_t *element_ptr, + double x, + double y, + uint32_t time_msec) +{ + wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_fake_element_t, element); + fake_element_ptr->orig_vmt.pointer_motion(element_ptr, x, y, time_msec); + fake_element_ptr->pointer_motion_called = true; + return (-1 <= x && x < fake_element_ptr->dimensions.width + 3 && + -2 < y && y < fake_element_ptr->dimensions.height + 4); +} + +/* ------------------------------------------------------------------------- */ +/** Handles 'button' events for the fake element. */ +bool fake_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr) +{ + wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_fake_element_t, element); + + fake_element_ptr->pointer_button_called = true; + memcpy( + &fake_element_ptr->pointer_button_event, + button_event_ptr, + sizeof(wlmtk_button_event_t)); + return true; +} + +/* ------------------------------------------------------------------------- */ +/** Handles 'enter' events for the fake element. */ +void fake_pointer_enter( + wlmtk_element_t *element_ptr) +{ + wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_fake_element_t, element); + fake_element_ptr->orig_vmt.pointer_enter(element_ptr); + fake_element_ptr->pointer_enter_called = true; +} + +/* ------------------------------------------------------------------------- */ +/** Handles 'leave' events for the fake element. */ +void fake_pointer_leave( + wlmtk_element_t *element_ptr) +{ + wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_fake_element_t, element); + fake_element_ptr->orig_vmt.pointer_leave(element_ptr); + fake_element_ptr->pointer_leave_called = true; +} + +/* == Unit tests =========================================================== */ + +static void test_init_fini(bs_test_t *test_ptr); +static void test_set_parent_container(bs_test_t *test_ptr); +static void test_set_get_position(bs_test_t *test_ptr); +static void test_get_dimensions(bs_test_t *test_ptr); +static void test_get_pointer_area(bs_test_t *test_ptr); +static void test_pointer_motion_leave(bs_test_t *test_ptr); +static void test_pointer_button(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_element_test_cases[] = { + { 1, "init_fini", test_init_fini }, + { 1, "set_parent_container", test_set_parent_container }, + { 1, "set_get_position", test_set_get_position }, + { 1, "get_dimensions", test_get_dimensions }, + { 1, "get_pointer_area", test_get_pointer_area }, + { 1, "pointer_motion_leave", test_pointer_motion_leave }, + { 1, "pointer_button", test_pointer_button }, + { 0, NULL, NULL } +}; + +/* ------------------------------------------------------------------------- */ +/** Exercises init() and fini() methods, verifies dtor forwarding. */ +void test_init_fini(bs_test_t *test_ptr) +{ + wlmtk_element_t element; + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_init(&element, NULL)); + wlmtk_element_extend(&element, &fake_element_vmt); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, element.vmt.destroy); + + wlmtk_element_fini(&element); + BS_TEST_VERIFY_EQ(test_ptr, NULL, element.vmt.destroy); +} + +/* ------------------------------------------------------------------------- */ +/** Tests set_parent_container, and that scene graph follows. */ +void test_set_parent_container(bs_test_t *test_ptr) +{ + wlmtk_element_t element; + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_init(&element, NULL)); + wlmtk_element_extend(&element, &fake_element_vmt); + + // Setting a parent without a scene graph tree will not set a node. + wlmtk_container_t parent_no_tree; + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_container_init(&parent_no_tree, NULL)); + wlmtk_element_set_parent_container(&element, &parent_no_tree); + BS_TEST_VERIFY_EQ(test_ptr, NULL, element.wlr_scene_node_ptr); + + wlmtk_element_set_parent_container(&element, NULL); + BS_TEST_VERIFY_EQ(test_ptr, NULL, element.wlr_scene_node_ptr); + wlmtk_container_fini(&parent_no_tree); + + // Setting a parent with a tree must create & attach the node there. + wlmtk_container_t *parent_ptr = wlmtk_container_create_fake_parent(); + BS_ASSERT(NULL != parent_ptr); + wlmtk_element_set_visible(&element, true); + wlmtk_element_set_parent_container(&element, parent_ptr); + BS_TEST_VERIFY_EQ( + test_ptr, + parent_ptr->wlr_scene_tree_ptr, + element.wlr_scene_node_ptr->parent); + BS_TEST_VERIFY_TRUE(test_ptr, element.wlr_scene_node_ptr->enabled); + + // Resetting the parent must also re-attach the node. + wlmtk_container_t *other_parent_ptr = wlmtk_container_create_fake_parent(); + BS_ASSERT(NULL != other_parent_ptr); + wlmtk_element_set_parent_container(&element, other_parent_ptr); + BS_TEST_VERIFY_EQ( + test_ptr, + other_parent_ptr->wlr_scene_tree_ptr, + element.wlr_scene_node_ptr->parent); + BS_TEST_VERIFY_TRUE(test_ptr, element.wlr_scene_node_ptr->enabled); + + // Clearing the parent most remove the node. + wlmtk_element_set_parent_container(&element, NULL); + BS_TEST_VERIFY_EQ(test_ptr, NULL, element.wlr_scene_node_ptr); + + wlmtk_container_destroy_fake_parent(other_parent_ptr); + wlmtk_container_destroy_fake_parent(parent_ptr); + wlmtk_element_fini(&element); +} + +/* ------------------------------------------------------------------------- */ +/** Tests get_position and set_position, and that scene graph follows. */ +void test_set_get_position(bs_test_t *test_ptr) +{ + wlmtk_element_t element; + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_init(&element, NULL)); + wlmtk_element_extend(&element, &fake_element_vmt); + + // Exercise, must not crash. + wlmtk_element_get_position(&element, NULL, NULL); + + int x, y; + wlmtk_element_get_position(&element, &x, &y); + BS_TEST_VERIFY_EQ(test_ptr, 0, x); + BS_TEST_VERIFY_EQ(test_ptr, 0, y); + + wlmtk_element_set_position(&element, 10, 20); + wlmtk_element_get_position(&element, &x, &y); + BS_TEST_VERIFY_EQ(test_ptr, 10, x); + BS_TEST_VERIFY_EQ(test_ptr, 20, y); + + wlmtk_container_t *fake_parent_ptr = wlmtk_container_create_fake_parent(); + BS_ASSERT(NULL != fake_parent_ptr); + wlmtk_element_set_parent_container(&element, fake_parent_ptr); + + BS_TEST_VERIFY_EQ(test_ptr, 10, element.wlr_scene_node_ptr->x); + BS_TEST_VERIFY_EQ(test_ptr, 20, element.wlr_scene_node_ptr->y); + + wlmtk_element_set_position(&element, 30, 40); + BS_TEST_VERIFY_EQ(test_ptr, 30, element.wlr_scene_node_ptr->x); + BS_TEST_VERIFY_EQ(test_ptr, 40, element.wlr_scene_node_ptr->y); + + wlr_scene_node_set_position(element.wlr_scene_node_ptr, 50, 60); + wlmtk_element_get_position(&element, &x, &y); + BS_TEST_VERIFY_EQ(test_ptr, 50, x); + BS_TEST_VERIFY_EQ(test_ptr, 60, y); + + wlmtk_element_set_parent_container(&element, NULL); + wlmtk_element_fini(&element); + wlmtk_container_destroy_fake_parent(fake_parent_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests get_dimensions. */ +void test_get_dimensions(bs_test_t *test_ptr) +{ + wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); + fake_element_ptr->dimensions.x = -10; + fake_element_ptr->dimensions.y = -20; + fake_element_ptr->dimensions.width = 42; + fake_element_ptr->dimensions.height = 21; + + // Must not crash. + wlmtk_element_get_dimensions( + &fake_element_ptr->element, NULL, NULL, NULL, NULL); + + int top, left, right, bottom; + wlmtk_element_get_dimensions( + &fake_element_ptr->element, &top, &left, &right, &bottom); + BS_TEST_VERIFY_EQ(test_ptr, -10, top); + BS_TEST_VERIFY_EQ(test_ptr, -20, left); + BS_TEST_VERIFY_EQ(test_ptr, 52, right); + BS_TEST_VERIFY_EQ(test_ptr, 41, bottom); + + struct wlr_box box = wlmtk_element_get_dimensions_box( + &fake_element_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, -10, box.x); + BS_TEST_VERIFY_EQ(test_ptr, -20, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 42, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 21, box.height); + + wlmtk_element_destroy(&fake_element_ptr->element); +} + +/* ------------------------------------------------------------------------- */ +/** Tests get_dimensions. */ +void test_get_pointer_area(bs_test_t *test_ptr) +{ + wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); + fake_element_ptr->dimensions.width = 42; + fake_element_ptr->dimensions.height = 21; + + // Must not crash. + wlmtk_element_get_pointer_area( + &fake_element_ptr->element, NULL, NULL, NULL, NULL); + + int top, left, right, bottom; + wlmtk_element_get_pointer_area( + &fake_element_ptr->element, &top, &left, &right, &bottom); + BS_TEST_VERIFY_EQ(test_ptr, -1, top); + BS_TEST_VERIFY_EQ(test_ptr, -2, left); + BS_TEST_VERIFY_EQ(test_ptr, 45, right); + BS_TEST_VERIFY_EQ(test_ptr, 25, bottom); + + wlmtk_element_destroy(&fake_element_ptr->element); +} + +/* ------------------------------------------------------------------------- */ +/** Exercises "pointer_motion" and "pointer_leave" methods. */ +void test_pointer_motion_leave(bs_test_t *test_ptr) +{ + wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); + BS_ASSERT(NULL != fake_element_ptr); + + BS_TEST_VERIFY_TRUE( + test_ptr, + isnan(fake_element_ptr->element.last_pointer_x)); + BS_TEST_VERIFY_TRUE( + test_ptr, + isnan(fake_element_ptr->element.last_pointer_y)); + + wlmtk_element_pointer_motion(&fake_element_ptr->element, 1.0, 2.0, 1234); + BS_TEST_VERIFY_TRUE(test_ptr, fake_element_ptr->pointer_enter_called); + BS_TEST_VERIFY_EQ(test_ptr, 1.0, fake_element_ptr->element.last_pointer_x); + BS_TEST_VERIFY_EQ(test_ptr, 2.0, fake_element_ptr->element.last_pointer_y); + BS_TEST_VERIFY_EQ( + test_ptr, + 1234, + fake_element_ptr->element.last_pointer_time_msec); + + wlmtk_element_pointer_motion(&fake_element_ptr->element, NAN, NAN, 1235); + BS_TEST_VERIFY_TRUE(test_ptr, fake_element_ptr->pointer_leave_called); + BS_TEST_VERIFY_TRUE( + test_ptr, + isnan(fake_element_ptr->element.last_pointer_x)); + BS_TEST_VERIFY_TRUE( + test_ptr, + isnan(fake_element_ptr->element.last_pointer_y)); + BS_TEST_VERIFY_EQ( + test_ptr, + 1235, + fake_element_ptr->element.last_pointer_time_msec); + + wlmtk_element_destroy(&fake_element_ptr->element); +} + +/* ------------------------------------------------------------------------- */ +/** Exercises "pointer_button" method. */ +void test_pointer_button(bs_test_t *test_ptr) +{ + wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); + BS_ASSERT(NULL != fake_element_ptr); + + wlmtk_button_event_t event = {}; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(&fake_element_ptr->element, &event)); + BS_TEST_VERIFY_TRUE(test_ptr, fake_element_ptr->pointer_button_called); + + wlmtk_element_destroy(&fake_element_ptr->element); +} + +/* == End of toolkit.c ===================================================== */ diff --git a/src/toolkit/element.h b/src/toolkit/element.h new file mode 100644 index 00000000..26e4867c --- /dev/null +++ b/src/toolkit/element.h @@ -0,0 +1,429 @@ +/* ========================================================================= */ +/** + * @file element.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_ELEMENT_H__ +#define __WLMTK_ELEMENT_H__ + +#include +#include + +#include "wlr/util/box.h" + +/** Forward declaration: Element. */ +typedef struct _wlmtk_element_t wlmtk_element_t; +/** Forward declaration: Element virtual method table. */ +typedef struct _wlmtk_element_vmt_t wlmtk_element_vmt_t; + +/** Forward declaration: Container. */ +typedef struct _wlmtk_container_t wlmtk_container_t; +struct wlr_scene_tree; + +#include "env.h" +#include "input.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Virtual method table for the element. */ +struct _wlmtk_element_vmt_t { + /** Abstract: Destroys the implementation of the element. */ + void (*destroy)(wlmtk_element_t *element_ptr); + + /** Abstract: Creates element's scene graph API node, child to wlr_scene_tree_ptr. */ + struct wlr_scene_node *(*create_scene_node)( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr); + + /** Abstract: Gets dimensions of the element, relative to the element's position. */ + void (*get_dimensions)( + wlmtk_element_t *element_ptr, + int *x1_ptr, + int *y1_ptr, + int *x2_ptr, + int *y2_ptr); + + /** Gets element area to accept pointer activity, relative to position. */ + void (*get_pointer_area)( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr); + + /** + * Indicates pointer motion into or within the element area to (x,y). + * + * The default implementation at @ref _wlmtk_element_pointer_motion updates + * @ref wlmtk_element_t::last_pointer_x, + * @ref wlmtk_element_t::last_pointer_y + * and @ref wlmtk_element_t::last_pointer_time_msec. + * + * Derived classes that overwrite this method are advised to call the + * initial implementation for keeping these internals updated. + * + * @param element_ptr + * @param x + * @param y + * @param time_msec + * + * @return Whether the motion is considered within the element's pointer + * area. If it returns true, the caller should consider this element + * as having pointer focus. + */ + bool (*pointer_motion)(wlmtk_element_t *element_ptr, + double x, double y, + uint32_t time_msec); + + /** + * Indicates pointer button event. + * + * @param element_ptr + * @param button_event_ptr + * + * @return true If the button event was consumed. + */ + bool (*pointer_button)(wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr); + + /** + * Indicates the pointer has entered the element's area. + * + * TODO(kaeser@gubbe.ch): pointer_enter and pointer_leave would better be + * handled as events, where clients subscribe via listeners. Consider + * changing that. + * + * @param element_ptr + */ + void (*pointer_enter)(wlmtk_element_t *element_ptr); + + /** + * Indicates the pointer has left the element's area. + * + * @param element_ptr + */ + void (*pointer_leave)(wlmtk_element_t *element_ptr); +}; + +/** State of an element. */ +struct _wlmtk_element_t { + /** + * X position of the element in pixels, relative to parent container. + * + * This value may be stale, if @ref wlmtk_element_t::wlr_scene_node_ptr is + * set and had been updated directly. Therefore, consider the value as + * "private", and access only through @ref wlmtk_element_get_position. + */ + int x; + /** + * Y position of the element, relative to the container. + * + * Same observations apply as for @ref wlmtk_element_t::x. + */ + int y; + + /** The container this element belongs to, if any. */ + wlmtk_container_t *parent_container_ptr; + /** The node of elements. */ + bs_dllist_node_t dlnode; + + /** Virtual method table for the element. */ + wlmtk_element_vmt_t vmt; + + /** Toolkit environment. */ + wlmtk_env_t *env_ptr; + + /** Points to the wlroots scene graph API node, if attached. */ + struct wlr_scene_node *wlr_scene_node_ptr; + + /** Whether the element is visible (drawn, when part of a scene graph). */ + bool visible; + + /** Listener for the `destroy` signal of `wlr_scene_node_ptr`. */ + struct wl_listener wlr_scene_node_destroy_listener; + + /** + * Horizontal pointer position from last @ref wlmtk_element_pointer_motion + * call. NAN if there was no motion call yet, or if the last motion call + * had NAN arguments. + * + * Does not imply that the element has pointer focus. + */ + double last_pointer_x; + /** + * Vertical pointer position from last @ref wlmtk_element_pointer_motion + * call. NAN if there was no motion call yet, or if the last motion call + * had NAN arguments. + * + * Does not imply that the element has pointer focus. + */ + double last_pointer_y; + /** Time of last @ref wlmtk_element_pointer_motion call, 0 otherwise. */ + uint32_t last_pointer_time_msec; + /** Whether the pointer is currently within the element's bounds. */ + bool pointer_inside; +}; + +/** + * Initializes the element. + * + * @param element_ptr + * @param env_ptr + * + * @return true on success. + */ +bool wlmtk_element_init(wlmtk_element_t *element_ptr, + wlmtk_env_t *env_ptr); + +/** + * Extends the element's virtual methods. + * + * @param element_ptr + * @param element_vmt_ptr + * + * @return The previous virtual method table. + */ +wlmtk_element_vmt_t wlmtk_element_extend( + wlmtk_element_t *element_ptr, + const wlmtk_element_vmt_t *element_vmt_ptr); + +/** + * Cleans up the element. + * + * @param element_ptr + */ +void wlmtk_element_fini( + wlmtk_element_t *element_ptr); + +/** Gets the dlnode from the element. */ +bs_dllist_node_t *wlmtk_dlnode_from_element( + wlmtk_element_t *element_ptr); +/** Gets the element from the dlnode. */ +wlmtk_element_t *wlmtk_element_from_dlnode( + bs_dllist_node_t *dlnode_ptr); + +/** + * Sets the parent container for the element. + * + * Will call @ref wlmtk_element_attach_to_scene_graph to align the scene graph + * with the new (or deleted) parent. + * + * Private: Should only be called by wlmtk_container_add_element, respectively + * wlmtk_container_remove_element ("friends"). + * + * @param element_ptr + * @param parent_container_ptr Pointer to the parent container, or NULL if + * the parent should be cleared. + */ +void wlmtk_element_set_parent_container( + wlmtk_element_t *element_ptr, + wlmtk_container_t *parent_container_ptr); + +/** + * Attaches or detaches the element to the parent's wlroots scene tree. + * + * If the element has a parent, and that parent is itself attached to the + * wlroots scene tree, this will either re-parent an already existing node, + * or invoke @ref wlmtk_element_vmt_t::create_scene_node to create and attach a + * new node to the paren'ts tree. + * Otherwise, it will clear any existing node. + * + * The function is idempotent. + * + * Private: Should only called by wlmtk_container_t methods, when there are + * changes to wlmtk_container_t::wlr_scene_tree. + * + * @param element_ptr + */ +void wlmtk_element_attach_to_scene_graph( + wlmtk_element_t *element_ptr); + +/** + * Sets the element's visibility. + * + * @param element_ptr + * @param visible + */ +void wlmtk_element_set_visible(wlmtk_element_t *element_ptr, bool visible); + +/** + * Returns the position of the element. + * + * @param element_ptr + * @param x_ptr Optional, may be NULL. + * @param y_ptr Optional, may be NULL. + */ +void wlmtk_element_get_position( + wlmtk_element_t *element_ptr, + int *x_ptr, + int *y_ptr); + +/** + * Sets the position of the element. + * + * @param element_ptr + * @param x + * @param y + */ +void wlmtk_element_set_position( + wlmtk_element_t *element_ptr, + int x, + int y); + +/** + * Gets the area that the element on which the element accepts pointer events. + * + * The area extents are relative to the element's position. By default, this + * overlaps with the element dimensions. Some elements (eg. a surface with + * further-extending sub-surfaces) may differ. + * + * @param element_ptr + * @param x1_ptr Leftmost position of pointer area. May be NULL. + * @param y1_ptr Topmost position of pointer area. May be NULL. + * @param x2_ptr Rightmost position of pointer area. May be NULL. + * @param y2_ptr Bottommost position of pointer area. May be NULL. + */ +static inline void wlmtk_element_get_pointer_area( + wlmtk_element_t *element_ptr, + int *x1_ptr, + int *y1_ptr, + int *x2_ptr, + int *y2_ptr) +{ + element_ptr->vmt.get_pointer_area( + element_ptr, x1_ptr, y1_ptr, x2_ptr, y2_ptr); +} + +/** + * Gets the dimensions of the element in pixels, relative to the position. + * + * @param element_ptr + * @param left_ptr Leftmost position. May be NULL. + * @param top_ptr Topmost position. May be NULL. + * @param right_ptr Rightmost position. Ma be NULL. + * @param bottom_ptr Bottommost position. May be NULL. + */ +static inline void wlmtk_element_get_dimensions( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr) +{ + element_ptr->vmt.get_dimensions( + element_ptr, left_ptr, top_ptr, right_ptr, bottom_ptr); +} + +/** + * Gets the element's dimensions in pixel as wlr_box, relative to the position. + * + * @param element_ptr + * + * @return A struct wlr_box that specifies the top-left corner of the element + * relative to it's position, and the element's total width and height. + */ +static inline struct wlr_box wlmtk_element_get_dimensions_box( + wlmtk_element_t *element_ptr) +{ + struct wlr_box box; + element_ptr->vmt.get_dimensions( + element_ptr, &box.x, &box.y, &box.width, &box.height); + box.width += box.x; + box.height += box.y; + return box; +} + +/** + * Passes a pointer motion event on to the element. + * + * Will forward to @ref wlmtk_element_vmt_t::pointer_motion, and (depending on + * that return value) trigger @ref wlmtk_element_vmt_t::pointer_enter of + * @ref wlmtk_element_vmt_t::pointer_leave calls. + * + * @param element_ptr + * @param x + * @param y + * @param time_msec + */ +bool wlmtk_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, + double y, + uint32_t time_msec); + +/** Calls @ref wlmtk_element_vmt_t::pointer_button. */ +static inline bool wlmtk_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr) +{ + return element_ptr->vmt.pointer_button(element_ptr, button_event_ptr); +} + +/** + * Virtual method: Calls the dtor of the element's implementation. + * + * The implementation is required to call @ref wlmtk_element_fini(). + * + * @param element_ptr + */ +static inline void wlmtk_element_destroy( + wlmtk_element_t *element_ptr) { + element_ptr->vmt.destroy(element_ptr); +} + +/** Unit tests for the element. */ +extern const bs_test_case_t wlmtk_element_test_cases[]; + +/** Fake element, useful for unit test. */ +typedef struct { + /** State of the element. */ + wlmtk_element_t element; + /** Original VMT. */ + wlmtk_element_vmt_t orig_vmt; + /** Dimensions of the fake element, in pixels. */ + struct wlr_box dimensions; + + /** Indicates @ref wlmtk_element_vmt_t::pointer_motion() was called. */ + bool pointer_motion_called; + + /** Indicates @ref wlmtk_element_vmt_t::pointer_enter() was called. */ + bool pointer_enter_called; + /** Indicates @ref wlmtk_element_vmt_t::pointer_leave() was called. */ + bool pointer_leave_called; + /** Indicates @ref wlmtk_element_vmt_t::pointer_button() was called. */ + bool pointer_button_called; + /** Last button event reveiced. */ + wlmtk_button_event_t pointer_button_event; +} wlmtk_fake_element_t; + +/** + * Ctor for the fake element, useful for tests. + * + * @return A pointer to @ref wlmtk_fake_element_t. Should be destroyed via + * @ref wlmtk_element_destroy, by passing the pointer to + * @ref wlmtk_fake_element_t::element as argument. + */ +wlmtk_fake_element_t *wlmtk_fake_element_create(void); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_ELEMENT_H__ */ +/* == End of element.h ===================================================== */ diff --git a/src/toolkit/env.c b/src/toolkit/env.c new file mode 100644 index 00000000..bbaaa68a --- /dev/null +++ b/src/toolkit/env.c @@ -0,0 +1,110 @@ +/* ========================================================================= */ +/** + * @file env.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "env.h" + +#include + +#define WLR_USE_UNSTABLE +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +/** State of the environment. */ +struct _wlmtk_env_t { + /** Points to a `wlr_cursor`. */ + struct wlr_cursor *wlr_cursor_ptr; + /** Points to a `wlr_xcursor_manager`. */ + struct wlr_xcursor_manager *wlr_xcursor_manager_ptr; + /** Points to a `wlr_seat`. */ + struct wlr_seat *wlr_seat_ptr; +}; + +/** Struct to identify a @ref wlmtk_env_cursor_t with the xcursor name. */ +typedef struct { + /** The cursor. */ + wlmtk_env_cursor_t cursor; + /** And the xcursor name. */ + const char *xcursor_name_ptr; +} wlmtk_env_cursor_lookup_t; + +/** Lookup table for xcursor names. */ +static const wlmtk_env_cursor_lookup_t _wlmtk_env_cursor_lookup[] = { + { WLMTK_CURSOR_DEFAULT, "default" }, + { WLMTK_CURSOR_RESIZE_S, "s-resize" }, + { WLMTK_CURSOR_RESIZE_SE, "se-resize" }, + { WLMTK_CURSOR_RESIZE_SW, "sw-resize" }, + { 0, NULL }, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +wlmtk_env_t *wlmtk_env_create( + struct wlr_cursor *wlr_cursor_ptr, + struct wlr_xcursor_manager *wlr_xcursor_manager_ptr, + struct wlr_seat *wlr_seat_ptr) +{ + wlmtk_env_t *env_ptr = logged_calloc(1, sizeof(wlmtk_env_t)); + if (NULL == env_ptr) return NULL; + + env_ptr->wlr_cursor_ptr = wlr_cursor_ptr; + env_ptr->wlr_xcursor_manager_ptr = wlr_xcursor_manager_ptr; + env_ptr->wlr_seat_ptr = wlr_seat_ptr; + + return env_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_env_destroy(wlmtk_env_t *env_ptr) +{ + free(env_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_env_set_cursor(wlmtk_env_t *env_ptr, wlmtk_env_cursor_t cursor) +{ + const wlmtk_env_cursor_lookup_t *lookup_ptr = &_wlmtk_env_cursor_lookup[0]; + for (; + NULL != lookup_ptr->xcursor_name_ptr && cursor != lookup_ptr->cursor; + ++lookup_ptr) ; + if (NULL == lookup_ptr->xcursor_name_ptr) { + bs_log(BS_FATAL, "No name for cursor %d", cursor); + return; + } + + if (NULL != env_ptr && + NULL != env_ptr->wlr_cursor_ptr && + NULL != env_ptr->wlr_xcursor_manager_ptr) { + wlr_cursor_set_xcursor( + env_ptr->wlr_cursor_ptr, + env_ptr->wlr_xcursor_manager_ptr, + lookup_ptr->xcursor_name_ptr); + } +} + +/* ------------------------------------------------------------------------- */ +struct wlr_seat *wlmtk_env_wlr_seat(wlmtk_env_t *env_ptr) +{ + return env_ptr->wlr_seat_ptr; +} + +/* == End of env.c ========================================================= */ diff --git a/src/toolkit/env.h b/src/toolkit/env.h new file mode 100644 index 00000000..9c55550a --- /dev/null +++ b/src/toolkit/env.h @@ -0,0 +1,90 @@ +/* ========================================================================= */ +/** + * @file env.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_ENV_H__ +#define __WLMTK_ENV_H__ + +/** Forward declaration: Environment. */ +typedef struct _wlmtk_env_t wlmtk_env_t; + +/** Forward declaration. */ +struct wlr_cursor; +/** Forward declaration. */ +struct wlr_seat; +/** Forward declaration. */ +struct wlr_xcursor_manager; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Cursor types. */ +typedef enum { + /** Default. */ + WLMTK_CURSOR_DEFAULT, + /** Resizing, southern border. */ + WLMTK_CURSOR_RESIZE_S, + /** Resizing, south-eastern corner. */ + WLMTK_CURSOR_RESIZE_SE, + /** Resizing, south-western corner. */ + WLMTK_CURSOR_RESIZE_SW, +} wlmtk_env_cursor_t; + +/** + * Creates an environment state from the cursor. + * + * @param wlr_cursor_ptr + * @param wlr_xcursor_manager_ptr + * @param wlr_seat_ptr + * + * @return An environment state or NULL on error. + */ +wlmtk_env_t *wlmtk_env_create( + struct wlr_cursor *wlr_cursor_ptr, + struct wlr_xcursor_manager *wlr_xcursor_manager_ptr, + struct wlr_seat *wlr_seat_ptr); + +/** + * Destroys the environment state. + * + * @param env_ptr + */ +void wlmtk_env_destroy(wlmtk_env_t *env_ptr); + +/** + * Sets a cursor. + * + * @param env_ptr + * @param cursor + */ +void wlmtk_env_set_cursor(wlmtk_env_t *env_ptr, wlmtk_env_cursor_t cursor); + +/** + * Returns the pointer to the wlr_seat. + * + * @param env_ptr + */ +struct wlr_seat *wlmtk_env_wlr_seat(wlmtk_env_t *env_ptr); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_ENV_H__ */ +/* == End of env.h ========================================================= */ diff --git a/src/toolkit/fsm.c b/src/toolkit/fsm.c new file mode 100644 index 00000000..20661522 --- /dev/null +++ b/src/toolkit/fsm.c @@ -0,0 +1,104 @@ +/* ========================================================================= */ +/** + * @file fsm.c + * Event-driven finite state machine. + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fsm.h" + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +void wlmtk_fsm_init( + wlmtk_fsm_t *fsm_ptr, + const wlmtk_fsm_transition_t *transitions, + int initial_state) +{ + fsm_ptr->transitions = transitions; + fsm_ptr->state = initial_state; +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_fsm_event( + wlmtk_fsm_t *fsm_ptr, + int event, + void *ud_ptr) +{ + for (const wlmtk_fsm_transition_t *transition_ptr = fsm_ptr->transitions; + 0 <= transition_ptr->state; + ++transition_ptr) { + if (transition_ptr->state == fsm_ptr->state && + transition_ptr->event == event) { + bool rv = true; + if (NULL != transition_ptr->handler) { + rv = transition_ptr->handler(fsm_ptr, ud_ptr); + } + fsm_ptr->state = transition_ptr->to_state; + return rv; + } + } + return false; +} + +/* == Unit tests =========================================================== */ + +static void test_event(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_fsm_test_cases[] = { + { 1, "event", test_event }, + { 0, NULL, NULL } +}; + +/** Test handler for the FSM unit test: Sets the bool to true. */ +static bool test_fsm_handler(__UNUSED__ wlmtk_fsm_t *fsm_ptr, void *ud_ptr) { + *((bool*)ud_ptr) = true; + return true; +} +/** Test transition table for the FSM unit test. */ +static const wlmtk_fsm_transition_t test_transitions[] = { + { 1, 100, 2, test_fsm_handler }, + { 2, 101, 3, NULL }, + WLMTK_FSM_TRANSITION_SENTINEL +}; + +/* ------------------------------------------------------------------------- */ +/** Tests FSM. */ +void test_event(bs_test_t *test_ptr) +{ + wlmtk_fsm_t fsm; + bool called = false; + + wlmtk_fsm_init(&fsm, test_transitions, 1); + BS_TEST_VERIFY_EQ(test_ptr, 1, fsm.state); + + // (1, 100) should trigger call to handler and move to (2). + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_fsm_event(&fsm, 100, &called)); + BS_TEST_VERIFY_EQ(test_ptr, 2, fsm.state); + BS_TEST_VERIFY_TRUE(test_ptr, called); + called = false; + + // (2, 100) is not defined. return false. + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_fsm_event(&fsm, 100, &called)); + + // (2, 101) is defined. No handler == no crash. moves to (3). + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_fsm_event(&fsm, 101, &called)); + BS_TEST_VERIFY_EQ(test_ptr, 3, fsm.state); + BS_TEST_VERIFY_FALSE(test_ptr, called); +} + +/* == End of fsm.c ========================================================= */ diff --git a/src/toolkit/fsm.h b/src/toolkit/fsm.h new file mode 100644 index 00000000..f43b7cc9 --- /dev/null +++ b/src/toolkit/fsm.h @@ -0,0 +1,102 @@ +/* ========================================================================= */ +/** + * @file fsm.h + * Event-driven finite state machine. + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_FSM_H__ +#define __WLMTK_FSM_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Forward declaration. */ +typedef struct _wlmtk_fsm_t wlmtk_fsm_t; + +/** State machine definition. */ +typedef struct { + /** State before receiving the event. */ + int state; + /** Event. */ + int event; + /** Upon having (state, event): State to transition to. */ + int to_state; + /** Handler for the activity at (state, event). */ + bool (*handler)(wlmtk_fsm_t *fsm_ptr, void *ud_ptr); +} wlmtk_fsm_transition_t; + +/** Finite state machine. State. */ +struct _wlmtk_fsm_t { + /** The transitions table. */ + const wlmtk_fsm_transition_t *transitions; + /** Current state. */ + int state; +}; + +/** Sentinel element for state transition table. */ +#define WLMTK_FSM_TRANSITION_SENTINEL { \ + .state = -1, \ + .event = -1, \ + .to_state = -1, \ + .handler = NULL, \ + } + +/** + * Initializes the finite-state machine. + * + * @param fsm_ptr + * @param transitions + * @param initial_state + */ +void wlmtk_fsm_init( + wlmtk_fsm_t *fsm_ptr, + const wlmtk_fsm_transition_t *transitions, + int initial_state); + +/** + * Handles an event for the finite-state machine. + * + * Will search for the transition matching (current state, event) and call the + * associate handler. + * + * @param fsm_ptr + * @param event + * @param ud_ptr + * + * @return If a matching transition was found: The return value of the + * associated handler (or true, if no handler was given). Otherwise, + * returns false. + */ +bool wlmtk_fsm_event( + wlmtk_fsm_t *fsm_ptr, + int event, + void *ud_ptr); + +/** Unit tests for the finite-state machine. */ +extern const bs_test_case_t wlmtk_fsm_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_FSM_H__ */ +/* == End of fsm.h ====================================================== */ diff --git a/src/toolkit/gfxbuf.c b/src/toolkit/gfxbuf.c index c8a2b528..d3fb84a5 100644 --- a/src/toolkit/gfxbuf.c +++ b/src/toolkit/gfxbuf.c @@ -51,8 +51,6 @@ static bool wlmaker_gfxbuf_impl_begin_data_ptr_access( static void wlmaker_gfxbuf_impl_end_data_ptr_access( struct wlr_buffer *wlr_buffer_ptr); - - /** Implementation callbacks for wlroots' `struct wlr_buffer`. */ static const struct wlr_buffer_impl wlmaker_gfxbuf_impl = { .destroy = wlmaker_gfxbuf_impl_destroy, @@ -85,6 +83,14 @@ struct wlr_buffer *bs_gfxbuf_create_wlr_buffer( return &gfxbuf_ptr->wlr_buffer; } +/* ------------------------------------------------------------------------- */ +void wlr_buffer_drop_nullify(struct wlr_buffer **wlr_buffer_ptr_ptr) +{ + if (NULL == *wlr_buffer_ptr_ptr) return; + wlr_buffer_drop(*wlr_buffer_ptr_ptr); + *wlr_buffer_ptr_ptr = NULL; +} + /* ------------------------------------------------------------------------- */ bs_gfxbuf_t *bs_gfxbuf_from_wlr_buffer( struct wlr_buffer *wlr_buffer_ptr) diff --git a/src/toolkit/gfxbuf.h b/src/toolkit/gfxbuf.h index ff46c5ee..fd96ff70 100644 --- a/src/toolkit/gfxbuf.h +++ b/src/toolkit/gfxbuf.h @@ -17,8 +17,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef __GFXBUF_H__ -#define __GFXBUF_H__ +#ifndef __WLMTK_GFXBUF_H__ +#define __WLMTK_GFXBUF_H__ #include #include @@ -45,6 +45,15 @@ struct wlr_buffer *bs_gfxbuf_create_wlr_buffer( unsigned width, unsigned height); +/** + * Drops a WLR buffer, and sets the pointer to NULL. + * + * @param wlr_buffer_ptr_ptr Points to a pointer to a WLR buffer. The pointer + * to the WLR buffer may be NULL; in that case, the + * call is a no-op. + */ +void wlr_buffer_drop_nullify(struct wlr_buffer **wlr_buffer_ptr_ptr); + /** * Returns the libbase graphics buffer for the `struct wlr_buffer`. * @@ -72,5 +81,5 @@ cairo_t *cairo_create_from_wlr_buffer(struct wlr_buffer *wlr_buffer_ptr); } // extern "C" #endif // __cplusplus -#endif /* __GFXBUF_H__ */ +#endif /* __WLMTK_GFXBUF_H__ */ /* == End of gfxbuf.h ====================================================== */ diff --git a/src/toolkit/input.h b/src/toolkit/input.h new file mode 100644 index 00000000..fbf18ef2 --- /dev/null +++ b/src/toolkit/input.h @@ -0,0 +1,56 @@ +/* ========================================================================= */ +/** + * @file input.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_INPUT_H__ +#define __WLMTK_INPUT_H__ + +// BTN_LEFT, BTN_RIGHT, ... +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Forward declaration: Button event. */ +typedef struct _wlmtk_button_event_t wlmtk_button_event_t; + +/** Button state. */ +typedef enum { + WLMTK_BUTTON_DOWN, + WLMTK_BUTTON_UP, + WLMTK_BUTTON_CLICK, + WLMTK_BUTTON_DOUBLE_CLICK, +} wlmtk_button_event_type_t; + +/** Button events. */ +struct _wlmtk_button_event_t { + /** Button for which the event applies: linux/input-event-codes.h */ + uint32_t button; + /** Type of the event: DOWN, UP, ... */ + wlmtk_button_event_type_t type; + /** Time of the button event, in milliseconds. */ + uint32_t time_msec; +}; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_INPUT_H__ */ +/* == End of input.h ======================================================= */ diff --git a/src/toolkit/primitives.c b/src/toolkit/primitives.c index d52ef5d6..92bffbe5 100644 --- a/src/toolkit/primitives.c +++ b/src/toolkit/primitives.c @@ -27,7 +27,7 @@ /* ------------------------------------------------------------------------- */ void wlmaker_primitives_cairo_fill( cairo_t *cairo_ptr, - const wlmaker_style_fill_t *fill_ptr) + const wlmtk_style_fill_t *fill_ptr) { cairo_surface_t *surface_ptr = cairo_get_target(cairo_ptr); uint32_t width = cairo_image_surface_get_width(surface_ptr); @@ -42,18 +42,18 @@ void wlmaker_primitives_cairo_fill_at( int y, unsigned width, unsigned height, - const wlmaker_style_fill_t *fill_ptr) + const wlmtk_style_fill_t *fill_ptr) { cairo_pattern_t *cairo_pattern_ptr; float r, g, b, alpha; switch (fill_ptr->type) { - case WLMAKER_STYLE_COLOR_SOLID: + case WLMTK_STYLE_COLOR_SOLID: bs_gfxbuf_argb8888_to_floats( fill_ptr->param.solid.color, &r, &g, &b, &alpha); cairo_pattern_ptr = cairo_pattern_create_rgba(r, g, b, alpha); break; - case WLMAKER_STYLE_COLOR_HGRADIENT: + case WLMTK_STYLE_COLOR_HGRADIENT: cairo_pattern_ptr = cairo_pattern_create_linear(0, 0, width, 0); bs_gfxbuf_argb8888_to_floats( fill_ptr->param.hgradient.from, &r, &g, &b, &alpha); @@ -65,7 +65,7 @@ void wlmaker_primitives_cairo_fill_at( cairo_pattern_ptr, 1, r, g, b, alpha); break; - case WLMAKER_STYLE_COLOR_DGRADIENT: + case WLMTK_STYLE_COLOR_DGRADIENT: cairo_pattern_ptr = cairo_pattern_create_linear(0, 0, width, height); bs_gfxbuf_argb8888_to_floats( fill_ptr->param.dgradient.from, &r, &g, &b, &alpha); @@ -240,8 +240,8 @@ void test_fill(bs_test_t *test_ptr) cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); // Solid fill. - wlmaker_style_fill_t fill_solid = { - .type = WLMAKER_STYLE_COLOR_SOLID, + wlmtk_style_fill_t fill_solid = { + .type = WLMTK_STYLE_COLOR_SOLID, .param = { .solid = { .color = 0xff4080c0} } }; wlmaker_primitives_cairo_fill(cairo_ptr, &fill_solid); @@ -249,8 +249,8 @@ void test_fill(bs_test_t *test_ptr) test_ptr, gfxbuf_ptr, "toolkit/primitive_fill_solid.png"); // Horizontal fill. - wlmaker_style_fill_t fill_hgradient = { - .type = WLMAKER_STYLE_COLOR_HGRADIENT, + wlmtk_style_fill_t fill_hgradient = { + .type = WLMTK_STYLE_COLOR_HGRADIENT, .param = { .hgradient = { .from = 0xff102040, .to = 0xff4080ff }} }; wlmaker_primitives_cairo_fill(cairo_ptr, &fill_hgradient); @@ -258,8 +258,8 @@ void test_fill(bs_test_t *test_ptr) test_ptr, gfxbuf_ptr, "toolkit/primitive_fill_hgradient.png"); // Diagonal fill. - wlmaker_style_fill_t fill_dgradient = { - .type = WLMAKER_STYLE_COLOR_DGRADIENT, + wlmtk_style_fill_t fill_dgradient = { + .type = WLMTK_STYLE_COLOR_DGRADIENT, .param = { .dgradient = { .from = 0xff102040, .to = 0xff4080ff }} }; wlmaker_primitives_cairo_fill(cairo_ptr, &fill_dgradient); diff --git a/src/toolkit/primitives.h b/src/toolkit/primitives.h index 8355608c..9125c146 100644 --- a/src/toolkit/primitives.h +++ b/src/toolkit/primitives.h @@ -17,8 +17,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef __PRIMITIVES_H__ -#define __PRIMITIVES_H__ +#ifndef __WLMTK_PRIMITIVES_H__ +#define __WLMTK_PRIMITIVES_H__ #include #include @@ -37,7 +37,7 @@ extern "C" { */ void wlmaker_primitives_cairo_fill( cairo_t *cairo_ptr, - const wlmaker_style_fill_t *fill_ptr); + const wlmtk_style_fill_t *fill_ptr); /** * Fills the cairo with the specified style at the specified rectangle. @@ -55,7 +55,7 @@ void wlmaker_primitives_cairo_fill_at( int y, unsigned width, unsigned height, - const wlmaker_style_fill_t *fill_ptr); + const wlmtk_style_fill_t *fill_ptr); /** * Sets the bezel color. @@ -146,5 +146,5 @@ extern const bs_test_case_t wlmaker_primitives_test_cases[]; } // extern "C" #endif // __cplusplus -#endif /* __PRIMITIVES_H__ */ +#endif /* __WLMTK_PRIMITIVES_H__ */ /* == End of primitives.h ================================================== */ diff --git a/src/toolkit/rectangle.c b/src/toolkit/rectangle.c new file mode 100644 index 00000000..b2482d46 --- /dev/null +++ b/src/toolkit/rectangle.c @@ -0,0 +1,318 @@ +/* ========================================================================= */ +/** + * @file rectangle.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rectangle.h" + +#include "container.h" +#include "util.h" + +#define WLR_USE_UNSTABLE +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +/** State of a unicolor rectangle. */ +struct _wlmtk_rectangle_t { + /** Superclass element. */ + wlmtk_element_t super_element; + /** Original virtual method table of the superclass element. */ + wlmtk_element_vmt_t orig_super_element_vmt; + + /** Width of the rectangle. */ + int width; + /** Height of the rectangle. */ + int height; + /** Color of the rectangle, as an ARGB8888 value. */ + uint32_t color; + + /** WLR rectangle. */ + struct wlr_scene_rect *wlr_scene_rect_ptr; + /** Listener for the `destroy` signal of `wlr_rect_buffer_ptr->node`. */ + struct wl_listener wlr_scene_rect_node_destroy_listener; +}; + +static void _wlmtk_rectangle_element_destroy(wlmtk_element_t *element_ptr); +static struct wlr_scene_node *_wlmtk_rectangle_element_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr); +static void _wlmtk_rectangle_get_dimensions( + wlmtk_element_t *element_ptr, + int *x1_ptr, + int *y1_ptr, + int *x2_ptr, + int *y2_ptr); +static void handle_wlr_scene_rect_node_destroy( + struct wl_listener *listener_ptr, + void *data_ptr); + +/* == Data ================================================================= */ + +/** Virtual method table of the rectangle, extending the element. */ +static const wlmtk_element_vmt_t _wlmtk_rectangle_element_vmt = { + .destroy = _wlmtk_rectangle_element_destroy, + .create_scene_node = _wlmtk_rectangle_element_create_scene_node, + .get_dimensions = _wlmtk_rectangle_get_dimensions, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +wlmtk_rectangle_t *wlmtk_rectangle_create( + wlmtk_env_t *env_ptr, + int width, + int height, + uint32_t color) +{ + wlmtk_rectangle_t *rectangle_ptr = logged_calloc( + 1, sizeof(wlmtk_rectangle_t)); + if (NULL == rectangle_ptr) return NULL; + rectangle_ptr->width = width; + rectangle_ptr->height = height; + wlmtk_rectangle_set_color(rectangle_ptr, color); + + if (!wlmtk_element_init(&rectangle_ptr->super_element, env_ptr)) { + wlmtk_rectangle_destroy(rectangle_ptr); + return NULL; + } + rectangle_ptr->orig_super_element_vmt = wlmtk_element_extend( + &rectangle_ptr->super_element, + &_wlmtk_rectangle_element_vmt); + + return rectangle_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_rectangle_destroy(wlmtk_rectangle_t *rectangle_ptr) +{ + if (NULL != rectangle_ptr->wlr_scene_rect_ptr) { + wlr_scene_node_destroy(&rectangle_ptr->wlr_scene_rect_ptr->node); + rectangle_ptr->wlr_scene_rect_ptr = NULL; + } + + wlmtk_element_fini(&rectangle_ptr->super_element); + free(rectangle_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_rectangle_set_size( + wlmtk_rectangle_t *rectangle_ptr, + int width, + int height) +{ + rectangle_ptr->width = width; + rectangle_ptr->height = height; + + if (NULL != rectangle_ptr->wlr_scene_rect_ptr) { + wlr_scene_rect_set_size( + rectangle_ptr->wlr_scene_rect_ptr, + rectangle_ptr->width, + rectangle_ptr->height); + } +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_rectangle_set_color( + wlmtk_rectangle_t *rectangle_ptr, + uint32_t color) +{ + rectangle_ptr->color = color; + + if (NULL != rectangle_ptr->wlr_scene_rect_ptr) { + float fcolor[4]; + bs_gfxbuf_argb8888_to_floats( + color, &fcolor[0], &fcolor[1], &fcolor[2], &fcolor[3]); + wlr_scene_rect_set_color(rectangle_ptr->wlr_scene_rect_ptr, fcolor); + } +} + +/* ------------------------------------------------------------------------- */ +wlmtk_element_t *wlmtk_rectangle_element(wlmtk_rectangle_t *rectangle_ptr) +{ + return &rectangle_ptr->super_element; +} + +/* ------------------------------------------------------------------------- */ +wlmtk_rectangle_t *wlmtk_rectangle_from_element(wlmtk_element_t *element_ptr) +{ + BS_ASSERT(element_ptr->vmt.destroy = _wlmtk_rectangle_element_destroy); + wlmtk_rectangle_t *rectangle_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_rectangle_t, super_element); + return rectangle_ptr; +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** Virtual dtor: Invoke the rectangle's dtor. */ +void _wlmtk_rectangle_element_destroy(wlmtk_element_t *element_ptr) +{ + wlmtk_rectangle_t *rectangle_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_rectangle_t, super_element); + wlmtk_rectangle_destroy(rectangle_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Implementation of the superclass wlmtk_element_t::create_scene_node method. + * + * Creates a `struct wlr_scene_rect` attached to `wlr_scene_tree_ptr`. + * + * @param element_ptr + * @param wlr_scene_tree_ptr + */ +struct wlr_scene_node *_wlmtk_rectangle_element_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr) +{ + wlmtk_rectangle_t *rectangle_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_rectangle_t, super_element); + + BS_ASSERT(NULL == rectangle_ptr->wlr_scene_rect_ptr); + float color[4]; + bs_gfxbuf_argb8888_to_floats( + rectangle_ptr->color, &color[0], &color[1], &color[2], &color[3]); + rectangle_ptr->wlr_scene_rect_ptr = wlr_scene_rect_create( + wlr_scene_tree_ptr, + rectangle_ptr->width, + rectangle_ptr->height, + color); + if (NULL == rectangle_ptr->wlr_scene_rect_ptr) return NULL; + + wlmtk_util_connect_listener_signal( + &rectangle_ptr->wlr_scene_rect_ptr->node.events.destroy, + &rectangle_ptr->wlr_scene_rect_node_destroy_listener, + handle_wlr_scene_rect_node_destroy); + return &rectangle_ptr->wlr_scene_rect_ptr->node; +} + +/* ------------------------------------------------------------------------- */ +/** + * Implementation of the element's get_dimensions method: Return dimensions. + * + * @param element_ptr + * @param x1_ptr 0. + * @param y1_ptr 0. + * @param x2_ptr Width. May be NULL. + * @param y2_ptr Height. May be NULL. + */ +void _wlmtk_rectangle_get_dimensions( + wlmtk_element_t *element_ptr, + int *x1_ptr, + int *y1_ptr, + int *x2_ptr, + int *y2_ptr) +{ + wlmtk_rectangle_t *rectangle_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_rectangle_t, super_element); + if (NULL != x1_ptr) *x1_ptr = 0; + if (NULL != y1_ptr) *y1_ptr = 0; + if (NULL != x2_ptr) *x2_ptr = rectangle_ptr->width; + if (NULL != y2_ptr) *y2_ptr = rectangle_ptr->height; +} + +/* ------------------------------------------------------------------------- */ +/** + * Handles the 'destroy' callback of wlr_scene_rect_ptr->node. + * + * Will reset the wlr_scene_rect_ptr value. Destruction of the node had + * been triggered (hence the callback). + * + * @param listener_ptr + * @param data_ptr + */ +void handle_wlr_scene_rect_node_destroy( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_rectangle_t *rectangle_ptr = BS_CONTAINER_OF( + listener_ptr, wlmtk_rectangle_t, wlr_scene_rect_node_destroy_listener); + + rectangle_ptr->wlr_scene_rect_ptr = NULL; + wl_list_remove(&rectangle_ptr->wlr_scene_rect_node_destroy_listener.link); +} + +/* == Unit Tests =========================================================== */ + +static void test_create_destroy(bs_test_t *test_ptr); +static void test_create_destroy_scene(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_rectangle_test_cases[] = { + { 1, "create_destroy", test_create_destroy }, + { 1, "create_destroy_scene", test_create_destroy_scene }, + { 0, NULL, NULL } +}; + +/* ------------------------------------------------------------------------- */ +/** Tests setup and teardown of rectangle. */ +void test_create_destroy(bs_test_t *test_ptr) +{ + wlmtk_rectangle_t *rectangle_ptr = wlmtk_rectangle_create( + NULL, 10, 20, 0x01020304); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, rectangle_ptr); + + int x1, y1, x2, y2; + wlmtk_element_get_dimensions( + &rectangle_ptr->super_element, &x1, &y1, &x2, &y2); + BS_TEST_VERIFY_EQ(test_ptr, 0, x1); + BS_TEST_VERIFY_EQ(test_ptr, 0, y1); + BS_TEST_VERIFY_EQ(test_ptr, 10, x2); + BS_TEST_VERIFY_EQ(test_ptr, 20, y2); + + BS_TEST_VERIFY_EQ( + test_ptr, + &rectangle_ptr->super_element, + wlmtk_rectangle_element(rectangle_ptr)); + BS_TEST_VERIFY_EQ( + test_ptr, + rectangle_ptr, + wlmtk_rectangle_from_element(&rectangle_ptr->super_element)); + + wlmtk_rectangle_destroy(rectangle_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests setup and teardown of rectangle, when attached to scene graph. */ +void test_create_destroy_scene(bs_test_t *test_ptr) +{ + wlmtk_container_t *c_ptr = wlmtk_container_create_fake_parent(); + wlmtk_rectangle_t *rectangle_ptr = wlmtk_rectangle_create( + NULL, 10, 20, 0x01020304); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, rectangle_ptr); + wlmtk_element_t *element_ptr = wlmtk_rectangle_element(rectangle_ptr); + + wlmtk_container_add_element(c_ptr, element_ptr); + + int x1, y1, x2, y2; + wlmtk_element_get_dimensions(element_ptr, &x1, &y1, &x2, &y2); + BS_TEST_VERIFY_EQ(test_ptr, 0, x1); + BS_TEST_VERIFY_EQ(test_ptr, 0, y1); + BS_TEST_VERIFY_EQ(test_ptr, 10, x2); + BS_TEST_VERIFY_EQ(test_ptr, 20, y2); + + BS_TEST_VERIFY_NEQ(test_ptr, NULL, rectangle_ptr->wlr_scene_rect_ptr); + + wlmtk_container_remove_element(c_ptr, element_ptr); + + wlmtk_element_destroy(element_ptr); + wlmtk_container_destroy_fake_parent(c_ptr); +} + +/* == End of rectangle.c =================================================== */ diff --git a/src/toolkit/rectangle.h b/src/toolkit/rectangle.h new file mode 100644 index 00000000..a1ef9f60 --- /dev/null +++ b/src/toolkit/rectangle.h @@ -0,0 +1,100 @@ +/* ========================================================================= */ +/** + * @file rectangle.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_RECTANGLE_H__ +#define __WLMTK_RECTANGLE_H__ + +#include "element.h" +#include "env.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Forward declaration: Rectangle state. */ +typedef struct _wlmtk_rectangle_t wlmtk_rectangle_t; + +/** + * Creates a rectangle. Useful for margins and borders. + * + * @param env_ptr + * @param width + * @param height + * @param color + * + * @return Pointer to the rectangle state or NULL on error. + */ +wlmtk_rectangle_t *wlmtk_rectangle_create( + wlmtk_env_t *env_ptr, + int width, + int height, + uint32_t color); + +/** + * Destroys the rectangle. + * + * @param rectangle_ptr + */ +void wlmtk_rectangle_destroy(wlmtk_rectangle_t *rectangle_ptr); + +/** + * Sets (or updates) the size of the rectangle. + * + * @param rectangle_ptr + * @param width + * @param height + */ +void wlmtk_rectangle_set_size( + wlmtk_rectangle_t *rectangle_ptr, + int width, + int height); + +/** + * Sets (or updates) the color of the rectangle. + * + * @param rectangle_ptr + * @param color + */ +void wlmtk_rectangle_set_color( + wlmtk_rectangle_t *rectangle_ptr, + uint32_t color); + +/** Returns the superclass @ref wlmtk_element_t of the rectangle. */ +wlmtk_element_t *wlmtk_rectangle_element(wlmtk_rectangle_t *rectangle_ptr); + +/** + * Gets the @ref wlmtk_rectangle_t instance from it's element superclass. + * + * Requires `element_ptr` as pointer to @ref wlmtk_rectangle_t::super_element. + * + * @param element_ptr + * + * @return The pointer to the @ref wlmtk_rectangle_t instance. + */ +wlmtk_rectangle_t *wlmtk_rectangle_from_element(wlmtk_element_t *element_ptr); + +/** Unit tests. */ +extern const bs_test_case_t wlmtk_rectangle_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_RECTANGLE_H__ */ +/* == End of rectangle.h =================================================== */ diff --git a/src/toolkit/resizebar.c b/src/toolkit/resizebar.c new file mode 100644 index 00000000..9fd33388 --- /dev/null +++ b/src/toolkit/resizebar.c @@ -0,0 +1,336 @@ +/* ========================================================================= */ +/** + * @file resizebar.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "resizebar.h" + +#include "box.h" +#include "buffer.h" +#include "gfxbuf.h" +#include "primitives.h" +#include "resizebar_area.h" + +#include + +#define WLR_USE_UNSTABLE +#include +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +/** State of the title bar. */ +struct _wlmtk_resizebar_t { + /** Superclass: Box. */ + wlmtk_box_t super_box; + + /** Current width of the resize bar. */ + unsigned width; + /** Style of the resize bar. */ + wlmtk_resizebar_style_t style; + + /** Background. */ + bs_gfxbuf_t *gfxbuf_ptr; + + /** Left element of the resizebar. */ + wlmtk_resizebar_area_t *left_area_ptr; + /** Center element of the resizebar. */ + wlmtk_resizebar_area_t *center_area_ptr; + /** Right element of the resizebar. */ + wlmtk_resizebar_area_t *right_area_ptr; +}; + +static void _wlmtk_resizebar_element_destroy(wlmtk_element_t *element_ptr); +static bool redraw_buffers(wlmtk_resizebar_t *resizebar_ptr, unsigned width); + +/* == Data ================================================================= */ + +/** Virtual method table extension for the resizebar's element superclass. */ +static const wlmtk_element_vmt_t resizebar_element_vmt = { + .destroy = _wlmtk_resizebar_element_destroy, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +wlmtk_resizebar_t *wlmtk_resizebar_create( + wlmtk_env_t *env_ptr, + wlmtk_window_t *window_ptr, + const wlmtk_resizebar_style_t *style_ptr) +{ + wlmtk_resizebar_t *resizebar_ptr = logged_calloc( + 1, sizeof(wlmtk_resizebar_t)); + if (NULL == resizebar_ptr) return NULL; + memcpy(&resizebar_ptr->style, style_ptr, sizeof(wlmtk_resizebar_style_t)); + BS_ASSERT(0 == resizebar_ptr->style.margin_style.width); + + if (!wlmtk_box_init(&resizebar_ptr->super_box, + env_ptr, + WLMTK_BOX_HORIZONTAL, + &resizebar_ptr->style.margin_style)) { + wlmtk_resizebar_destroy(resizebar_ptr); + return NULL; + } + wlmtk_element_extend( + &resizebar_ptr->super_box.super_container.super_element, + &resizebar_element_vmt); + + resizebar_ptr->left_area_ptr = wlmtk_resizebar_area_create( + window_ptr, env_ptr, WLR_EDGE_LEFT | WLR_EDGE_BOTTOM); + if (NULL == resizebar_ptr->left_area_ptr) { + wlmtk_resizebar_destroy(resizebar_ptr); + return NULL; + } + wlmtk_box_add_element_front( + &resizebar_ptr->super_box, + wlmtk_resizebar_area_element(resizebar_ptr->left_area_ptr)); + + resizebar_ptr->center_area_ptr = wlmtk_resizebar_area_create( + window_ptr, env_ptr, WLR_EDGE_BOTTOM); + if (NULL == resizebar_ptr->center_area_ptr) { + wlmtk_resizebar_destroy(resizebar_ptr); + return NULL; + } + wlmtk_box_add_element_back( + &resizebar_ptr->super_box, + wlmtk_resizebar_area_element(resizebar_ptr->center_area_ptr)); + + resizebar_ptr->right_area_ptr = wlmtk_resizebar_area_create( + window_ptr, env_ptr, WLR_EDGE_RIGHT | WLR_EDGE_BOTTOM); + if (NULL == resizebar_ptr->right_area_ptr) { + wlmtk_resizebar_destroy(resizebar_ptr); + return NULL; + } + wlmtk_box_add_element_back( + &resizebar_ptr->super_box, + wlmtk_resizebar_area_element(resizebar_ptr->right_area_ptr)); + + return resizebar_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_resizebar_destroy(wlmtk_resizebar_t *resizebar_ptr) +{ + if (NULL != resizebar_ptr->right_area_ptr) { + wlmtk_box_remove_element( + &resizebar_ptr->super_box, + wlmtk_resizebar_area_element(resizebar_ptr->right_area_ptr)); + wlmtk_resizebar_area_destroy(resizebar_ptr->right_area_ptr); + resizebar_ptr->right_area_ptr = NULL; + } + if (NULL != resizebar_ptr->center_area_ptr) { + wlmtk_box_remove_element( + &resizebar_ptr->super_box, + wlmtk_resizebar_area_element(resizebar_ptr->center_area_ptr)); + wlmtk_resizebar_area_destroy(resizebar_ptr->center_area_ptr); + resizebar_ptr->center_area_ptr = NULL; + } + if (NULL != resizebar_ptr->left_area_ptr) { + wlmtk_box_remove_element( + &resizebar_ptr->super_box, + wlmtk_resizebar_area_element(resizebar_ptr->left_area_ptr)); + + wlmtk_resizebar_area_destroy(resizebar_ptr->left_area_ptr); + resizebar_ptr->left_area_ptr = NULL; + } + + if (NULL != resizebar_ptr->gfxbuf_ptr) { + bs_gfxbuf_destroy(resizebar_ptr->gfxbuf_ptr); + resizebar_ptr->gfxbuf_ptr = NULL; + } + + wlmtk_box_fini(&resizebar_ptr->super_box); + free(resizebar_ptr); +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_resizebar_set_width( + wlmtk_resizebar_t *resizebar_ptr, + unsigned width) +{ + if (resizebar_ptr->width == width) return true; + if (!redraw_buffers(resizebar_ptr, width)) return false; + BS_ASSERT(width == resizebar_ptr->width); + BS_ASSERT(width == resizebar_ptr->gfxbuf_ptr->width); + + int right_corner_width = BS_MIN( + (int)width, (int)resizebar_ptr->style.corner_width); + int left_corner_width = BS_MAX( + 0, BS_MIN((int)width - right_corner_width, + (int)resizebar_ptr->style.corner_width)); + int center_width = BS_MAX( + 0, (int)width - right_corner_width - left_corner_width); + + wlmtk_element_set_visible( + wlmtk_resizebar_area_element(resizebar_ptr->left_area_ptr), + 0 < left_corner_width); + wlmtk_element_set_visible( + wlmtk_resizebar_area_element(resizebar_ptr->center_area_ptr), + 0 < center_width); + wlmtk_element_set_visible( + wlmtk_resizebar_area_element(resizebar_ptr->right_area_ptr), + 0 < right_corner_width); + + if (!wlmtk_resizebar_area_redraw( + resizebar_ptr->left_area_ptr, + resizebar_ptr->gfxbuf_ptr, + 0, left_corner_width, + &resizebar_ptr->style)) { + return false; + } + if (!wlmtk_resizebar_area_redraw( + resizebar_ptr->center_area_ptr, + resizebar_ptr->gfxbuf_ptr, + left_corner_width, center_width, + &resizebar_ptr->style)) { + return false; + } + if (!wlmtk_resizebar_area_redraw( + resizebar_ptr->right_area_ptr, + resizebar_ptr->gfxbuf_ptr, + left_corner_width + center_width, right_corner_width, + &resizebar_ptr->style)) { + return false; + } + + wlmtk_container_update_layout(&resizebar_ptr->super_box.super_container); + return true; +} + +/* ------------------------------------------------------------------------- */ +wlmtk_element_t *wlmtk_resizebar_element(wlmtk_resizebar_t *resizebar_ptr) +{ + return &resizebar_ptr->super_box.super_container.super_element; +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** Virtual destructor: Wraps to our dtor. */ +void _wlmtk_resizebar_element_destroy(wlmtk_element_t *element_ptr) +{ + wlmtk_resizebar_t *resizebar_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_resizebar_t, + super_box.super_container.super_element); + wlmtk_resizebar_destroy(resizebar_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Redraws the resizebar's background in appropriate size. */ +bool redraw_buffers(wlmtk_resizebar_t *resizebar_ptr, unsigned width) +{ + cairo_t *cairo_ptr; + + bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create( + width, resizebar_ptr->style.height); + if (NULL == gfxbuf_ptr) return false; + cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); + if (NULL == cairo_ptr) { + bs_gfxbuf_destroy(gfxbuf_ptr); + return false; + } + wlmaker_primitives_cairo_fill(cairo_ptr, &resizebar_ptr->style.fill); + cairo_destroy(cairo_ptr); + + if (NULL != resizebar_ptr->gfxbuf_ptr) { + bs_gfxbuf_destroy(resizebar_ptr->gfxbuf_ptr); + } + resizebar_ptr->gfxbuf_ptr = gfxbuf_ptr; + resizebar_ptr->width = width; + return true; +} + +/* == Unit tests =========================================================== */ + +static void test_create_destroy(bs_test_t *test_ptr); +static void test_variable_width(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_resizebar_test_cases[] = { + { 1, "create_destroy", test_create_destroy }, + { 1, "variable_width", test_variable_width }, + { 0, NULL, NULL } +}; + +/* ------------------------------------------------------------------------- */ +/** Exercises @ref wlmtk_resizebar_create and @ref wlmtk_resizebar_destroy. */ +void test_create_destroy(bs_test_t *test_ptr) +{ + wlmtk_fake_window_t *fake_window_ptr = wlmtk_fake_window_create(); + wlmtk_resizebar_style_t style = {}; + wlmtk_resizebar_t *resizebar_ptr = wlmtk_resizebar_create( + NULL, fake_window_ptr->window_ptr, &style); + + BS_TEST_VERIFY_NEQ(test_ptr, NULL, resizebar_ptr); + + wlmtk_element_destroy(wlmtk_resizebar_element(resizebar_ptr)); + wlmtk_fake_window_destroy(fake_window_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Performs resizing and verifies the elements are shown as expected. */ +void test_variable_width(bs_test_t *test_ptr) +{ + wlmtk_fake_window_t *fake_window_ptr = wlmtk_fake_window_create(); + wlmtk_resizebar_style_t style = { .height = 7, .corner_width = 16 }; + wlmtk_resizebar_t *resizebar_ptr = wlmtk_resizebar_create( + NULL, fake_window_ptr->window_ptr, &style); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, resizebar_ptr); + + wlmtk_element_t *left_elem_ptr = wlmtk_resizebar_area_element( + resizebar_ptr->left_area_ptr); + wlmtk_element_t *center_elem_ptr = wlmtk_resizebar_area_element( + resizebar_ptr->center_area_ptr); + wlmtk_element_t *right_elem_ptr = wlmtk_resizebar_area_element( + resizebar_ptr->right_area_ptr); + + // Zero width. Zero visibility. + BS_TEST_VERIFY_FALSE(test_ptr, left_elem_ptr->visible); + BS_TEST_VERIFY_FALSE(test_ptr, center_elem_ptr->visible); + BS_TEST_VERIFY_FALSE(test_ptr, right_elem_ptr->visible); + + // Sufficient space for all the elements. + BS_TEST_VERIFY_TRUE( + test_ptr, wlmtk_resizebar_set_width(resizebar_ptr, 33)); + BS_TEST_VERIFY_TRUE(test_ptr, left_elem_ptr->visible); + BS_TEST_VERIFY_TRUE(test_ptr, center_elem_ptr->visible); + BS_TEST_VERIFY_TRUE(test_ptr, right_elem_ptr->visible); + BS_TEST_VERIFY_EQ(test_ptr, 16, center_elem_ptr->x); + BS_TEST_VERIFY_EQ(test_ptr, 17, right_elem_ptr->x); + + // Not enough space for the center element with all margins. + BS_TEST_VERIFY_TRUE( + test_ptr, wlmtk_resizebar_set_width(resizebar_ptr, 32)); + BS_TEST_VERIFY_TRUE(test_ptr, left_elem_ptr->visible); + BS_TEST_VERIFY_FALSE(test_ptr, center_elem_ptr->visible); + BS_TEST_VERIFY_TRUE(test_ptr, right_elem_ptr->visible); + BS_TEST_VERIFY_EQ(test_ptr, 16, right_elem_ptr->x); + + // Not enough space for center and left element. + BS_TEST_VERIFY_TRUE( + test_ptr, wlmtk_resizebar_set_width(resizebar_ptr, 16)); + BS_TEST_VERIFY_FALSE(test_ptr, left_elem_ptr->visible); + BS_TEST_VERIFY_FALSE(test_ptr, center_elem_ptr->visible); + BS_TEST_VERIFY_TRUE(test_ptr, right_elem_ptr->visible); + BS_TEST_VERIFY_EQ(test_ptr, 0, right_elem_ptr->x); + + wlmtk_element_destroy(wlmtk_resizebar_element(resizebar_ptr)); + wlmtk_fake_window_destroy(fake_window_ptr); +} + +/* == End of resizebar.c =================================================== */ diff --git a/src/toolkit/resizebar.h b/src/toolkit/resizebar.h new file mode 100644 index 00000000..f0382ae9 --- /dev/null +++ b/src/toolkit/resizebar.h @@ -0,0 +1,103 @@ +/* ========================================================================= */ +/** + * @file resizebar.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_RESIZEBAR_H__ +#define __WLMTK_RESIZEBAR_H__ + +/** Forward declaration: Title bar. */ +typedef struct _wlmtk_resizebar_t wlmtk_resizebar_t; +/** Forward declaration. */ +struct wlr_cursor; +/** Forward declaration. */ +struct wlr_xcursor_manager; + + +#include "element.h" +#include "primitives.h" +#include "window.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Style options for the resizebar. */ +typedef struct { + /** Fill style for the complete resizebar. */ + wlmtk_style_fill_t fill; + /** Height of the resize bar. */ + unsigned height; + /** Width of the corners. */ + unsigned corner_width; + /** Width of the bezel. */ + uint32_t bezel_width; + /** Style of the margin within the resizebar. */ + wlmtk_margin_style_t margin_style; +} wlmtk_resizebar_style_t; + +/** + * Creates the resize bar. + * + * @param env_ptr + * @param window_ptr + * @param style_ptr + * + * @return Pointer to the resizebar state, or NULL on error. + */ +wlmtk_resizebar_t *wlmtk_resizebar_create( + wlmtk_env_t *env_ptr, + wlmtk_window_t *window_ptr, + const wlmtk_resizebar_style_t *style_ptr); + +/** + * Destroys the resize bar. + * + * @param resizebar_ptr + */ +void wlmtk_resizebar_destroy(wlmtk_resizebar_t *resizebar_ptr); + +/** + * Sets the width of the resize bar. + * + * @param resizebar_ptr + * @param width + * + * @return true on success. + */ +bool wlmtk_resizebar_set_width( + wlmtk_resizebar_t * resizebar_ptr, + unsigned width); + +/** + * Returns the super Element of the resizebar. + * + * @param resizebar_ptr + * + * @return Pointer to the element. + */ +wlmtk_element_t *wlmtk_resizebar_element(wlmtk_resizebar_t *resizebar_ptr); + +/** Unit test cases. */ +extern const bs_test_case_t wlmtk_resizebar_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_RESIZEBAR_H__ */ +/* == End of resizebar.h ================================================== */ diff --git a/src/toolkit/resizebar_area.c b/src/toolkit/resizebar_area.c new file mode 100644 index 00000000..87cb1295 --- /dev/null +++ b/src/toolkit/resizebar_area.c @@ -0,0 +1,364 @@ +/* ========================================================================= */ +/** + * @file resizebar_area.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "resizebar_area.h" + +#include "box.h" +#include "buffer.h" +#include "gfxbuf.h" +#include "primitives.h" +#include "window.h" + +#include + +#define WLR_USE_UNSTABLE +#include +#include +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +/** State of an element of the resize bar. */ +struct _wlmtk_resizebar_area_t { + /** Superclass: Buffer. */ + wlmtk_buffer_t super_buffer; + /** Original virtual method table of the superclass element. */ + wlmtk_element_vmt_t orig_super_element_vmt; + + /** WLR buffer holding the buffer in released state. */ + struct wlr_buffer *released_wlr_buffer_ptr; + /** WLR buffer holding the buffer in pressed state. */ + struct wlr_buffer *pressed_wlr_buffer_ptr; + + /** Whether the area is currently pressed or not. */ + bool pressed; + + /** Window to which the resize bar area belongs. To initiate resizing. */ + wlmtk_window_t *window_ptr; + /** Edges that the resizebar area controls. */ + uint32_t edges; + + /** The cursor to use when having pointer focus. */ + wlmtk_env_cursor_t cursor; +}; + +static void _wlmtk_resizebar_area_element_destroy( + wlmtk_element_t *element_ptr); +static bool _wlmtk_resizebar_area_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, double y, + uint32_t time_msec); +static bool _wlmtk_resizebar_area_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr); + +static void draw_state(wlmtk_resizebar_area_t *resizebar_area_ptr); +static struct wlr_buffer *create_buffer( + bs_gfxbuf_t *gfxbuf_ptr, + unsigned position, + unsigned width, + const wlmtk_resizebar_style_t *style_ptr, + bool pressed); + +/* ========================================================================= */ + +/** Buffer implementation for title of the title bar. */ +static const wlmtk_element_vmt_t resizebar_area_element_vmt = { + .destroy = _wlmtk_resizebar_area_element_destroy, + .pointer_motion = _wlmtk_resizebar_area_element_pointer_motion, + .pointer_button = _wlmtk_resizebar_area_element_pointer_button, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +wlmtk_resizebar_area_t *wlmtk_resizebar_area_create( + wlmtk_window_t *window_ptr, + wlmtk_env_t *env_ptr, + uint32_t edges) +{ + wlmtk_resizebar_area_t *resizebar_area_ptr = logged_calloc( + 1, sizeof(wlmtk_resizebar_area_t)); + if (NULL == resizebar_area_ptr) return NULL; + BS_ASSERT(NULL != window_ptr); + resizebar_area_ptr->window_ptr = window_ptr; + resizebar_area_ptr->edges = edges; + + resizebar_area_ptr->cursor = WLMTK_CURSOR_DEFAULT; + switch (resizebar_area_ptr->edges) { + case WLR_EDGE_BOTTOM: + resizebar_area_ptr->cursor = WLMTK_CURSOR_RESIZE_S; + break; + case WLR_EDGE_BOTTOM | WLR_EDGE_LEFT: + resizebar_area_ptr->cursor = WLMTK_CURSOR_RESIZE_SW; + break; + case WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT: + resizebar_area_ptr->cursor = WLMTK_CURSOR_RESIZE_SE; + break; + default: + bs_log(BS_ERROR, "Unsupported edge %"PRIx32, edges); + } + + if (!wlmtk_buffer_init(&resizebar_area_ptr->super_buffer, env_ptr)) { + wlmtk_resizebar_area_destroy(resizebar_area_ptr); + return NULL; + } + resizebar_area_ptr->orig_super_element_vmt = wlmtk_element_extend( + &resizebar_area_ptr->super_buffer.super_element, + &resizebar_area_element_vmt); + + draw_state(resizebar_area_ptr); + return resizebar_area_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_resizebar_area_destroy( + wlmtk_resizebar_area_t *resizebar_area_ptr) +{ + wlr_buffer_drop_nullify( + &resizebar_area_ptr->released_wlr_buffer_ptr); + wlr_buffer_drop_nullify( + &resizebar_area_ptr->pressed_wlr_buffer_ptr); + + wlmtk_buffer_fini(&resizebar_area_ptr->super_buffer); + free(resizebar_area_ptr); +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_resizebar_area_redraw( + wlmtk_resizebar_area_t *resizebar_area_ptr, + bs_gfxbuf_t *gfxbuf_ptr, + unsigned position, + unsigned width, + const wlmtk_resizebar_style_t *style_ptr) +{ + struct wlr_buffer *released_wlr_buffer_ptr = create_buffer( + gfxbuf_ptr, position, width, style_ptr, false); + struct wlr_buffer *pressed_wlr_buffer_ptr = create_buffer( + gfxbuf_ptr, position, width, style_ptr, true); + + if (NULL == released_wlr_buffer_ptr || + NULL == pressed_wlr_buffer_ptr) { + wlr_buffer_drop_nullify(&released_wlr_buffer_ptr); + wlr_buffer_drop_nullify(&pressed_wlr_buffer_ptr); + return false; + } + + wlr_buffer_drop_nullify( + &resizebar_area_ptr->released_wlr_buffer_ptr); + resizebar_area_ptr->released_wlr_buffer_ptr = released_wlr_buffer_ptr; + wlr_buffer_drop_nullify( + &resizebar_area_ptr->pressed_wlr_buffer_ptr); + resizebar_area_ptr->pressed_wlr_buffer_ptr = pressed_wlr_buffer_ptr; + + draw_state(resizebar_area_ptr); + return true; +} + +/* ------------------------------------------------------------------------- */ +wlmtk_element_t *wlmtk_resizebar_area_element( + wlmtk_resizebar_area_t *resizebar_area_ptr) +{ + return &resizebar_area_ptr->super_buffer.super_element; +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** Dtor. */ +void _wlmtk_resizebar_area_element_destroy(wlmtk_element_t *element_ptr) +{ + wlmtk_resizebar_area_t *resizebar_area_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_resizebar_area_t, super_buffer.super_element); + wlmtk_resizebar_area_destroy(resizebar_area_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** See @ref wlmtk_element_vmt_t::pointer_motion. */ +bool _wlmtk_resizebar_area_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, + double y, + uint32_t time_msec) +{ + wlmtk_resizebar_area_t *resizebar_area_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_resizebar_area_t, super_buffer.super_element); + resizebar_area_ptr->orig_super_element_vmt.pointer_motion( + element_ptr, x, y, time_msec); + + wlmtk_env_set_cursor(element_ptr->env_ptr, resizebar_area_ptr->cursor); + return true; +} + +/* ------------------------------------------------------------------------- */ +/** See @ref wlmtk_element_vmt_t::pointer_button. */ +bool _wlmtk_resizebar_area_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr) +{ + wlmtk_resizebar_area_t *resizebar_area_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_resizebar_area_t, super_buffer.super_element); + + if (button_event_ptr->button != BTN_LEFT) return false; + + switch (button_event_ptr->type) { + case WLMTK_BUTTON_DOWN: + resizebar_area_ptr->pressed = true; + + wlmtk_window_request_resize( + resizebar_area_ptr->window_ptr, + resizebar_area_ptr->edges); + draw_state(resizebar_area_ptr); + break; + + case WLMTK_BUTTON_UP: + resizebar_area_ptr->pressed = false; + draw_state(resizebar_area_ptr); + break; + + default: + break; + } + + return true; +} + +/* ------------------------------------------------------------------------- */ +/** + * Draws the buffer in current state (released or pressed). + * + * @param resizebar_area_ptr + */ +void draw_state(wlmtk_resizebar_area_t *resizebar_area_ptr) +{ + if (!resizebar_area_ptr->pressed) { + wlmtk_buffer_set( + &resizebar_area_ptr->super_buffer, + resizebar_area_ptr->released_wlr_buffer_ptr); + } else { + wlmtk_buffer_set( + &resizebar_area_ptr->super_buffer, + resizebar_area_ptr->pressed_wlr_buffer_ptr); + } +} + +/* ------------------------------------------------------------------------- */ +/** + * Creates a resizebar area texture. + * + * @param gfxbuf_ptr + * @param position + * @param width + * @param style_ptr + * @param pressed + * + * @return A pointer to a newly allocated `struct wlr_buffer`. + */ +struct wlr_buffer *create_buffer( + bs_gfxbuf_t *gfxbuf_ptr, + unsigned position, + unsigned width, + const wlmtk_resizebar_style_t *style_ptr, + bool pressed) +{ + struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( + width, style_ptr->height); + if (NULL == wlr_buffer_ptr) return NULL; + + bs_gfxbuf_copy_area( + bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr), 0, 0, + gfxbuf_ptr, position, 0, width, style_ptr->height); + + cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); + if (NULL == cairo_ptr) { + wlr_buffer_drop(wlr_buffer_ptr); + return false; + } + wlmaker_primitives_draw_bezel_at( + cairo_ptr, 0, 0, width, + style_ptr->height, style_ptr->bezel_width, !pressed); + cairo_destroy(cairo_ptr); + + return wlr_buffer_ptr; +} + +/* == Unit tests =========================================================== */ + +static void test_area(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_resizebar_area_test_cases[] = { + { 1, "area", test_area }, + { 0, NULL, NULL } +}; + +/* ------------------------------------------------------------------------- */ +/** Tests the area behaviour. */ +void test_area(bs_test_t *test_ptr) +{ + wlmtk_fake_window_t *fake_window_ptr = wlmtk_fake_window_create(); + + wlmtk_resizebar_area_t *area_ptr = wlmtk_resizebar_area_create( + fake_window_ptr->window_ptr, NULL, WLR_EDGE_BOTTOM); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, area_ptr); + wlmtk_element_t *element_ptr = wlmtk_resizebar_area_element(area_ptr); + + // Draw and verify release state. + wlmtk_resizebar_style_t style = { .height = 7, .bezel_width = 1.0 }; + bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(30, 7); + bs_gfxbuf_clear(gfxbuf_ptr, 0xff604020); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_resizebar_area_redraw(area_ptr, gfxbuf_ptr, 10, 12, &style)); + bs_gfxbuf_destroy(gfxbuf_ptr); + BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( + test_ptr, + bs_gfxbuf_from_wlr_buffer(area_ptr->super_buffer.wlr_buffer_ptr), + "toolkit/resizebar_area_released.png"); + BS_TEST_VERIFY_FALSE(test_ptr, fake_window_ptr->request_resize_called); + + // Pointer must be inside the button for accepting DOWN. + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(element_ptr, 1, 1, 0)); + // Button down: pressed. + wlmtk_button_event_t button = { + .button = BTN_LEFT, .type = WLMTK_BUTTON_DOWN + }; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(element_ptr, &button)); + BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( + test_ptr, + bs_gfxbuf_from_wlr_buffer(area_ptr->super_buffer.wlr_buffer_ptr), + "toolkit/resizebar_area_pressed.png"); + + // TODO(kaeser@gubbe.ch): Should verify setting the cursor. + BS_TEST_VERIFY_TRUE(test_ptr, fake_window_ptr->request_resize_called); + BS_TEST_VERIFY_EQ( + test_ptr, + WLR_EDGE_BOTTOM, + fake_window_ptr->request_resize_edges); + + wlmtk_element_destroy(element_ptr); + wlmtk_fake_window_destroy(fake_window_ptr); +} + +/* == End of resizebar_area.c ============================================== */ diff --git a/src/toolkit/resizebar_area.h b/src/toolkit/resizebar_area.h new file mode 100644 index 00000000..c52e178f --- /dev/null +++ b/src/toolkit/resizebar_area.h @@ -0,0 +1,88 @@ +/* ========================================================================= */ +/** + * @file resizebar_area.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_RESIZEBAR_AREA_H__ +#define __WLMTK_RESIZEBAR_AREA_H__ + +#include + +/** Forward declaration: Element of the resizebar. */ +typedef struct _wlmtk_resizebar_area_t wlmtk_resizebar_area_t ; + + +#include "resizebar.h" +#include "window.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Creates a resizebar button. + * + * @param window_ptr + * @param env_ptr + * @param edges + * + * @return Pointer to the resizebar button. + */ +wlmtk_resizebar_area_t *wlmtk_resizebar_area_create( + wlmtk_window_t *window_ptr, + wlmtk_env_t *env_ptr, + uint32_t edges); + +/** + * Destroys the resizebar element. + * + * @param resizebar_area_ptr + */ +void wlmtk_resizebar_area_destroy( + wlmtk_resizebar_area_t *resizebar_area_ptr); + +/** + * Redraws the element, with updated position and width. + * + * @param resizebar_area_ptr + * @param gfxbuf_ptr + * @param position + * @param width + * @param style_ptr + * + * @return true on success. + */ +bool wlmtk_resizebar_area_redraw( + wlmtk_resizebar_area_t *resizebar_area_ptr, + bs_gfxbuf_t *gfxbuf_ptr, + unsigned position, + unsigned width, + const wlmtk_resizebar_style_t *style_ptr); + +/** Returns the button's super_buffer.super_element address. */ +wlmtk_element_t *wlmtk_resizebar_area_element( + wlmtk_resizebar_area_t *resizebar_area_ptr); + +/** Unit test cases. */ +extern const bs_test_case_t wlmtk_resizebar_area_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_RESIZEBAR_AREA_H__ */ +/* == End of resizebar_area.h ============================================== */ diff --git a/src/toolkit/style.h b/src/toolkit/style.h index 3b87aeba..7b8c05af 100644 --- a/src/toolkit/style.h +++ b/src/toolkit/style.h @@ -17,8 +17,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef __STYLE_H__ -#define __STYLE_H__ +#ifndef __WLMTK_STYLE_H__ +#define __WLMTK_STYLE_H__ #include @@ -29,19 +29,19 @@ extern "C" { /** Specifies the type of coloring to use for the fill. */ typedef enum { /** Horizontal color gradient. */ - WLMAKER_STYLE_COLOR_SOLID, + WLMTK_STYLE_COLOR_SOLID, /** Horizontal color gradient. */ - WLMAKER_STYLE_COLOR_HGRADIENT, + WLMTK_STYLE_COLOR_HGRADIENT, /** Diagonal color gradient, top-left to bottom-right. */ - WLMAKER_STYLE_COLOR_DGRADIENT + WLMTK_STYLE_COLOR_DGRADIENT // TODO(kaeser@gubbe.ch): Add VGRADIENT. -} wlmaker_style_fill_type_t; +} wlmtk_style_fill_type_t; /** Specifies the color for a solid fill. */ typedef struct { /** Color to start from, as ARGB 8888. Left, for the HGRADIENT type. */ uint32_t color; -} wlmaker_style_color_solid_data_t; +} wlmtk_style_color_solid_data_t; /** Specifies the two colors to span a gradient between. */ typedef struct { @@ -49,26 +49,34 @@ typedef struct { uint32_t from; /** Color to end with, as ARGB 8888. Right, for the HGRADIENT type. */ uint32_t to; -} wlmaker_style_color_gradient_data_t; +} wlmtk_style_color_gradient_data_t; /** Specification for the fill of the titlebar. */ typedef struct { /** The type of fill to apply. */ - wlmaker_style_fill_type_t type; + wlmtk_style_fill_type_t type; /** Parameters for the fill. */ union { /** Solid color. */ - wlmaker_style_color_solid_data_t solid; + wlmtk_style_color_solid_data_t solid; /** Horizontal color gradient. */ - wlmaker_style_color_gradient_data_t hgradient; + wlmtk_style_color_gradient_data_t hgradient; /** Diagonal color gradient. */ - wlmaker_style_color_gradient_data_t dgradient; + wlmtk_style_color_gradient_data_t dgradient; } param; -} wlmaker_style_fill_t; +} wlmtk_style_fill_t; + +/** Specifies color and width of a margin. */ +typedef struct { + /** Width of the margin. */ + int width; + /** Color of the margin. */ + uint32_t color; +} wlmtk_margin_style_t; #ifdef __cplusplus } // extern "C" #endif // __cplusplus -#endif /* __STYLE_H__ */ +#endif /* __WLMTK_STYLE_H__ */ /* == End of style.h ================================================== */ diff --git a/src/toolkit/surface.c b/src/toolkit/surface.c new file mode 100644 index 00000000..4de9aa14 --- /dev/null +++ b/src/toolkit/surface.c @@ -0,0 +1,594 @@ +/* ========================================================================= */ +/** + * @file surface.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "surface.h" + +#include "element.h" +#include "gfxbuf.h" +#include "util.h" + +#define WLR_USE_UNSTABLE +#include +#include +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +static void _wlmtk_surface_element_get_dimensions( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr); +static void _wlmtk_surface_element_get_pointer_area( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr); +static void _wlmtk_surface_element_pointer_leave(wlmtk_element_t *element_ptr); +static bool _wlmtk_surface_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, + double y, + uint32_t time_msec); +static bool _wlmtk_surface_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr); + +/* == Data ================================================================= */ + +/** Method table for the element's virtual methods. */ +static const wlmtk_element_vmt_t surface_element_vmt = { + .get_dimensions = _wlmtk_surface_element_get_dimensions, + .get_pointer_area = _wlmtk_surface_element_get_pointer_area, + .pointer_leave = _wlmtk_surface_element_pointer_leave, + .pointer_motion = _wlmtk_surface_element_pointer_motion, + .pointer_button = _wlmtk_surface_element_pointer_button, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +bool wlmtk_surface_init( + wlmtk_surface_t *surface_ptr, + struct wlr_surface *wlr_surface_ptr, + wlmtk_env_t *env_ptr) +{ + BS_ASSERT(NULL != surface_ptr); + memset(surface_ptr, 0, sizeof(wlmtk_surface_t)); + + if (!wlmtk_element_init(&surface_ptr->super_element, env_ptr)) { + wlmtk_surface_fini(surface_ptr); + return false; + } + surface_ptr->orig_super_element_vmt = wlmtk_element_extend( + &surface_ptr->super_element, &surface_element_vmt); + + surface_ptr->wlr_surface_ptr = wlr_surface_ptr; + return true; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_surface_fini(wlmtk_surface_t *surface_ptr) +{ + wlmtk_element_fini(&surface_ptr->super_element); + memset(surface_ptr, 0, sizeof(wlmtk_surface_t)); +} + +/* ------------------------------------------------------------------------- */ +wlmtk_surface_vmt_t wlmtk_surface_extend( + wlmtk_surface_t *surface_ptr, + const wlmtk_surface_vmt_t *surface_vmt_ptr) +{ + wlmtk_surface_vmt_t orig_vmt = surface_ptr->vmt; + + if (NULL != surface_vmt_ptr->request_size) { + surface_ptr->vmt.request_size = surface_vmt_ptr->request_size; + } + if (NULL != surface_vmt_ptr->request_close) { + surface_ptr->vmt.request_close = surface_vmt_ptr->request_close; + } + if (NULL != surface_vmt_ptr->set_activated) { + surface_ptr->vmt.set_activated = surface_vmt_ptr->set_activated; + } + + return orig_vmt; +} + +/* ------------------------------------------------------------------------- */ +wlmtk_element_t *wlmtk_surface_element(wlmtk_surface_t *surface_ptr) +{ + return &surface_ptr->super_element; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_surface_get_size( + wlmtk_surface_t *surface_ptr, + int *width_ptr, + int *height_ptr) +{ + if (NULL != width_ptr) *width_ptr = surface_ptr->committed_width; + if (NULL != height_ptr) *height_ptr = surface_ptr->committed_height; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_surface_commit_size( + wlmtk_surface_t *surface_ptr, + __UNUSED__ uint32_t serial, + int width, + int height) +{ + // TODO(kaeser@gubbe.ch): don't update layout if size didn't change. + + if (surface_ptr->committed_width != width || + surface_ptr->committed_height != height) { + surface_ptr->committed_width = width; + surface_ptr->committed_height = height; + } + + if (NULL != surface_ptr->super_element.parent_container_ptr) { + wlmtk_container_update_layout( + surface_ptr->super_element.parent_container_ptr); + } +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** + * Implementation of the element's get_dimensions method: Return dimensions. + * + * @param element_ptr + * @param left_ptr Leftmost position. May be NULL. + * @param top_ptr Topmost position. May be NULL. + * @param right_ptr Rightmost position. Ma be NULL. + * @param bottom_ptr Bottommost position. May be NULL. + */ +void _wlmtk_surface_element_get_dimensions( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr) +{ + wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_surface_t, super_element); + + if (NULL != left_ptr) *left_ptr = 0; + if (NULL != top_ptr) *top_ptr = 0; + if (NULL != right_ptr) *right_ptr = surface_ptr->committed_width; + if (NULL != bottom_ptr) *bottom_ptr = surface_ptr->committed_height; +} + +/* ------------------------------------------------------------------------- */ +/** + * Overwrites the element's get_pointer_area method: Returns the extents of + * the surface and all subsurfaces. + * + * @param element_ptr + * @param left_ptr Leftmost position. May be NULL. + * @param top_ptr Topmost position. May be NULL. + * @param right_ptr Rightmost position. Ma be NULL. + * @param bottom_ptr Bottommost position. May be NULL. + */ +void _wlmtk_surface_element_get_pointer_area( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr) +{ + wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_surface_t, super_element); + + struct wlr_box box; + if (NULL == surface_ptr->wlr_surface_ptr) { + box.x = 0; + box.y = 0; + box.width = surface_ptr->committed_width; + box.height = surface_ptr->committed_height; + } else { + wlr_surface_get_extends(surface_ptr->wlr_surface_ptr, &box); + } + + if (NULL != left_ptr) *left_ptr = box.x; + if (NULL != top_ptr) *top_ptr = box.y; + if (NULL != right_ptr) *right_ptr = box.width - box.x; + if (NULL != bottom_ptr) *bottom_ptr = box.height - box.y; +} + +/* ------------------------------------------------------------------------- */ +/** + * Implements the element's leave method: If there's a WLR (sub)surface + * currently holding focus, that will be cleared. + * + * @param element_ptr + */ +void _wlmtk_surface_element_pointer_leave(wlmtk_element_t *element_ptr) +{ + wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_surface_t, super_element); + + // If the current surface's parent is our surface: clear it. + struct wlr_surface *focused_wlr_surface_ptr = + wlmtk_env_wlr_seat(surface_ptr->super_element.env_ptr + )->pointer_state.focused_surface; + if (NULL != focused_wlr_surface_ptr && + wlr_surface_get_root_surface(focused_wlr_surface_ptr) == + surface_ptr->wlr_surface_ptr) { + wlr_seat_pointer_clear_focus( + wlmtk_env_wlr_seat(surface_ptr->super_element.env_ptr)); + } +} + +/* ------------------------------------------------------------------------- */ +/** + * Pass pointer motion events to client's surface. + * + * Identifies the surface (or sub-surface) at the given coordinates, and pass + * on the motion event to that surface. If needed, will update the seat's + * pointer focus. + * + * @param element_ptr + * @param x Pointer horizontal position, relative to this + * element's node. + * @param y Pointer vertical position, relative to this + * element's node. + * @param time_msec + * + * @return Whether if the motion is within the area. + */ +bool _wlmtk_surface_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, + double y, + uint32_t time_msec) +{ + wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_surface_t, super_element); + + surface_ptr->orig_super_element_vmt.pointer_motion( + element_ptr, x, y, time_msec); + + if (NULL == surface_ptr->super_element.wlr_scene_node_ptr) return false; + + // Get the layout local coordinates of the node, so we can adjust the + // node-local (x, y) for the `wlr_scene_node_at` call. + int lx, ly; + if (!wlr_scene_node_coords( + surface_ptr->super_element.wlr_scene_node_ptr, &lx, &ly)) { + return false; + } + // Get the node below the cursor. Return if there's no buffer node. + double node_x, node_y; + struct wlr_scene_node *wlr_scene_node_ptr = wlr_scene_node_at( + surface_ptr->super_element.wlr_scene_node_ptr, + x + lx, y + ly, &node_x, &node_y); + + if (NULL == wlr_scene_node_ptr || + WLR_SCENE_NODE_BUFFER != wlr_scene_node_ptr->type) { + return false; + } + + struct wlr_scene_buffer *wlr_scene_buffer_ptr = + wlr_scene_buffer_from_node(wlr_scene_node_ptr); + struct wlr_scene_surface *wlr_scene_surface_ptr = + wlr_scene_surface_try_from_buffer(wlr_scene_buffer_ptr); + if (NULL == wlr_scene_surface_ptr) { + return true; + } + + BS_ASSERT(surface_ptr->wlr_surface_ptr == + wlr_surface_get_root_surface(wlr_scene_surface_ptr->surface)); + wlr_seat_pointer_notify_enter( + wlmtk_env_wlr_seat(surface_ptr->super_element.env_ptr), + wlr_scene_surface_ptr->surface, + node_x, node_y); + wlr_seat_pointer_notify_motion( + wlmtk_env_wlr_seat(surface_ptr->super_element.env_ptr), + time_msec, + node_x, node_y); + return true; +} + +/* ------------------------------------------------------------------------- */ +/** + * Passes pointer button event further to the focused surface, if any. + * + * The actual passing is handled by `wlr_seat`. Here we just verify that the + * currently-focused surface (or sub-surface) is part of this surface. + * + * @param element_ptr + * @param button_event_ptr + * + * @return Whether the button event was consumed. + */ +bool _wlmtk_surface_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr) +{ + wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_surface_t, super_element); + + // Complain if the surface isn't part of our responsibility. + struct wlr_surface *focused_wlr_surface_ptr = + wlmtk_env_wlr_seat(surface_ptr->super_element.env_ptr + )->pointer_state.focused_surface; + if (NULL == focused_wlr_surface_ptr) return false; + // TODO(kaeser@gubbe.ch): Dragging the pointer from an activated window + // over to a non-activated window will trigger the condition here on the + // WLMTK_BUTTON_UP event. Needs a test and fixing. + BS_ASSERT(surface_ptr->wlr_surface_ptr == + wlr_surface_get_root_surface(focused_wlr_surface_ptr)); + + // We're only forwarding PRESSED & RELEASED events. + if (WLMTK_BUTTON_DOWN == button_event_ptr->type || + WLMTK_BUTTON_UP == button_event_ptr->type) { + wlr_seat_pointer_notify_button( + wlmtk_env_wlr_seat(surface_ptr->super_element.env_ptr), + button_event_ptr->time_msec, + button_event_ptr->button, + (button_event_ptr->type == WLMTK_BUTTON_DOWN) ? + WLR_BUTTON_PRESSED : WLR_BUTTON_RELEASED); + return true; + } + return false; +} + +/* == Fake surface methods ================================================= */ + +static void _wlmtk_fake_surface_element_destroy( + wlmtk_element_t *element_ptr); +static struct wlr_scene_node *_wlmtk_fake_surface_element_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr); +static bool _wlmtk_fake_surface_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, double y, + __UNUSED__ uint32_t time_msec); +static bool _wlmtk_fake_surface_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr); +static void _wlmtk_fake_surface_element_pointer_leave( + wlmtk_element_t *element_ptr); + +/** Extensions to the surface's super elements virtual methods. */ +static const wlmtk_element_vmt_t _wlmtk_fake_surface_element_vmt = { + .destroy = _wlmtk_fake_surface_element_destroy, + .create_scene_node = _wlmtk_fake_surface_element_create_scene_node, + .pointer_motion = _wlmtk_fake_surface_element_pointer_motion, + .pointer_button = _wlmtk_fake_surface_element_pointer_button, + .pointer_leave = _wlmtk_fake_surface_element_pointer_leave, +}; + +static uint32_t _wlmtk_fake_surface_request_size( + wlmtk_surface_t *surface_ptr, + int width, + int height); +static void _wlmtk_fake_surface_request_close( + wlmtk_surface_t *surface_ptr); +static void _wlmtk_fake_surface_set_activated( + wlmtk_surface_t *surface_ptr, + bool activated); + +/** Virtual method table for the fake surface. */ +static const wlmtk_surface_vmt_t _wlmtk_fake_surface_vmt = { + .request_size = _wlmtk_fake_surface_request_size, + .request_close = _wlmtk_fake_surface_request_close, + .set_activated = _wlmtk_fake_surface_set_activated, +}; + +/* ------------------------------------------------------------------------- */ +wlmtk_fake_surface_t *wlmtk_fake_surface_create(void) +{ + wlmtk_fake_surface_t *fake_surface_ptr = logged_calloc( + 1, sizeof(wlmtk_fake_surface_t)); + if (NULL == fake_surface_ptr) return NULL; + + wlmtk_surface_init(&fake_surface_ptr->surface, NULL, NULL); + wlmtk_surface_extend(&fake_surface_ptr->surface, &_wlmtk_fake_surface_vmt); + wlmtk_element_extend( + &fake_surface_ptr->surface.super_element, + &_wlmtk_fake_surface_element_vmt); + return fake_surface_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_fake_surface_destroy(wlmtk_fake_surface_t *fake_surface_ptr) +{ + wlmtk_surface_fini(&fake_surface_ptr->surface); + free(fake_surface_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_fake_surface_commit(wlmtk_fake_surface_t *fake_surface_ptr) +{ + wlmtk_surface_commit_size( + &fake_surface_ptr->surface, + fake_surface_ptr->serial, + fake_surface_ptr->requested_width, + fake_surface_ptr->requested_height); + + if (NULL != fake_surface_ptr->surface.super_element.wlr_scene_node_ptr) { + struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( + fake_surface_ptr->surface.committed_width, + fake_surface_ptr->surface.committed_height); + BS_ASSERT(NULL != wlr_buffer_ptr); + + struct wlr_scene_buffer *wlr_scene_buffer_ptr = wlr_scene_buffer_from_node( + fake_surface_ptr->surface.super_element.wlr_scene_node_ptr); + BS_ASSERT(NULL != wlr_scene_buffer_ptr); + + wlr_scene_buffer_set_buffer(wlr_scene_buffer_ptr, wlr_buffer_ptr); + wlr_buffer_drop(wlr_buffer_ptr); + } +} + +/* ------------------------------------------------------------------------- */ +/** Fake implementation of the dtor, @ref wlmtk_element_vmt_t::destroy. */ +void _wlmtk_fake_surface_element_destroy( + wlmtk_element_t *element_ptr) +{ + wlmtk_fake_surface_t *fake_surface_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_fake_surface_t, surface.super_element); + wlmtk_fake_surface_destroy(fake_surface_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Fake implementation of @ref wlmtk_element_vmt_t::create_scene_node. */ +struct wlr_scene_node *_wlmtk_fake_surface_element_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr) +{ + wlmtk_fake_surface_t *fake_surface_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_fake_surface_t, surface.super_element); + + struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( + fake_surface_ptr->surface.committed_width, + fake_surface_ptr->surface.committed_height); + BS_ASSERT(NULL != wlr_buffer_ptr); + + struct wlr_scene_buffer *wlr_scene_buffer_ptr = wlr_scene_buffer_create( + wlr_scene_tree_ptr, wlr_buffer_ptr); + wlr_buffer_drop(wlr_buffer_ptr); + return &wlr_scene_buffer_ptr->node; +} + +/* ------------------------------------------------------------------------- */ +/** Fake for @ref wlmtk_element_vmt_t::pointer_motion. True if in committed. */ +bool _wlmtk_fake_surface_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, double y, + __UNUSED__ uint32_t time_msec) +{ + wlmtk_fake_surface_t *fake_surface_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_fake_surface_t, surface.super_element); + + return (0 <= x && x < fake_surface_ptr->surface.committed_width && + 0 <= y && y < fake_surface_ptr->surface.committed_height); +} + +/* ------------------------------------------------------------------------- */ +/** Fake for @ref wlmtk_element_vmt_t::pointer_button. Returns true. */ +bool _wlmtk_fake_surface_element_pointer_button( + __UNUSED__ wlmtk_element_t *element_ptr, + __UNUSED__ const wlmtk_button_event_t *button_event_ptr) +{ + return true; +} + +/* ------------------------------------------------------------------------- */ +/** Fake for @ref wlmtk_element_vmt_t::pointer_leave. Does nothing. */ +void _wlmtk_fake_surface_element_pointer_leave( + __UNUSED__ wlmtk_element_t *element_ptr) +{ + // Nothing to do. +} + +/* ------------------------------------------------------------------------- */ +/** Fake implementation of @ref wlmtk_surface_vmt_t::request_size. */ +uint32_t _wlmtk_fake_surface_request_size( + wlmtk_surface_t *surface_ptr, + int width, + int height) +{ + wlmtk_fake_surface_t *fake_surface_ptr = BS_CONTAINER_OF( + surface_ptr, wlmtk_fake_surface_t, surface); + fake_surface_ptr->requested_width = width; + fake_surface_ptr->requested_height = height; + return fake_surface_ptr->serial; +} + +/* ------------------------------------------------------------------------- */ +/** Records that @ref wlmtk_surface_request_close was called. */ +void _wlmtk_fake_surface_request_close(wlmtk_surface_t *surface_ptr) +{ + wlmtk_fake_surface_t *fake_surface_ptr = BS_CONTAINER_OF( + surface_ptr, wlmtk_fake_surface_t, surface); + fake_surface_ptr->request_close_called = true; +} + +/* ------------------------------------------------------------------------- */ +/** Sets the surface's activated status. */ +void _wlmtk_fake_surface_set_activated( + wlmtk_surface_t *surface_ptr, + bool activated) +{ + wlmtk_fake_surface_t *fake_surface_ptr = BS_CONTAINER_OF( + surface_ptr, wlmtk_fake_surface_t, surface); + fake_surface_ptr->activated = activated; +} + +/* == Unit tests =========================================================== */ + +static void test_init_fini(bs_test_t *test_ptr); +static void test_fake_commit(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_surface_test_cases[] = { + { 1, "init_fini", test_init_fini }, + { 1, "fake_commit", test_fake_commit }, + { 0, NULL, NULL } +}; + +/* ------------------------------------------------------------------------- */ +/** Tests setup and teardown. */ +void test_init_fini(bs_test_t *test_ptr) +{ + wlmtk_surface_t surface; + + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_surface_init(&surface, NULL, NULL)); + + BS_TEST_VERIFY_EQ( + test_ptr, + &surface.super_element, + wlmtk_surface_element(&surface)); + + wlmtk_surface_fini(&surface); +} + +/* ------------------------------------------------------------------------- */ +/** Exercises the request_size / commit flow. */ +void test_fake_commit(bs_test_t *test_ptr) +{ + wlmtk_fake_surface_t *fake_surface_ptr = wlmtk_fake_surface_create(); + int w, h; + + fake_surface_ptr->serial = 42; + + BS_TEST_VERIFY_EQ( + test_ptr, + 42, + wlmtk_surface_request_size(&fake_surface_ptr->surface, 200, 100)); + + wlmtk_surface_get_size(&fake_surface_ptr->surface, &w, &h); + BS_TEST_VERIFY_EQ(test_ptr, 0, w); + BS_TEST_VERIFY_EQ(test_ptr, 0, h); + + wlmtk_fake_surface_commit(fake_surface_ptr); + wlmtk_surface_get_size(&fake_surface_ptr->surface, &w, &h); + BS_TEST_VERIFY_EQ(test_ptr, 200, w); + BS_TEST_VERIFY_EQ(test_ptr, 100, h); + + wlmtk_fake_surface_destroy(fake_surface_ptr); +} + +/* == End of surface.c ===================================================== */ diff --git a/src/toolkit/surface.h b/src/toolkit/surface.h new file mode 100644 index 00000000..728f0eda --- /dev/null +++ b/src/toolkit/surface.h @@ -0,0 +1,208 @@ +/* ========================================================================= */ +/** + * @file surface.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_SURFACE_H__ +#define __WLMTK_SURFACE_H__ + +#include + +/** Forward declaration: State of a toolkit's WLR surface. */ +typedef struct _wlmtk_surface_t wlmtk_surface_t; +/** Forward declaration: Virtual method table of the toolkit's WLR surface. */ +typedef struct _wlmtk_surface_vmt_t wlmtk_surface_vmt_t; +/** Forward declaration: State of fake surface, for tests. */ +typedef struct _wlmtk_fake_surface_t wlmtk_fake_surface_t; + +#include "element.h" +#include "env.h" +#include "window.h" + +/** Forward declaration. */ +struct wlr_surface; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Virtual method table of the surface. */ +struct _wlmtk_surface_vmt_t { + /** Abstract: Requests width and height of the surface. Returns serial. */ + uint32_t (*request_size)(wlmtk_surface_t *surface_ptr, + int width, + int height); + /** Abstract: Requests the surface to close. */ + void (*request_close)(wlmtk_surface_t *surface_ptr); + /** Abstract: Sets whether the surface is activated (keyboard focus). */ + void (*set_activated)(wlmtk_surface_t *surface_ptr, bool activated); +}; + +/** State of a `struct wlr_surface`, encapsuled for toolkit. */ +struct _wlmtk_surface_t { + /** Super class of the surface: An element. */ + wlmtk_element_t super_element; + /** Virtual method table of the super element before extending it. */ + wlmtk_element_vmt_t orig_super_element_vmt; + + /** The surface's virtual method table. */ + wlmtk_surface_vmt_t vmt; + + /** The `struct wlr_surface` wrapped. */ + struct wlr_surface *wlr_surface_ptr; + + /** Committed width of the surface, in pixels. */ + int committed_width; + /** Committed height of the surface, in pixels. */ + int committed_height; +}; + +/** + * Initializes the surface. + * + * @param surface_ptr + * @param wlr_surface_ptr + * @param env_ptr + * + * @return true on success. + */ +bool wlmtk_surface_init( + wlmtk_surface_t *surface_ptr, + struct wlr_surface *wlr_surface_ptr, + wlmtk_env_t *env_ptr); + +/** + * Un-initializes the surface. + * + * @param surface_ptr + */ +void wlmtk_surface_fini(wlmtk_surface_t *surface_ptr); + +/** + * Extends the surface's virtual methods. + * + * @param surface_ptr + * @param surface_vmt_ptr + * + * @return The earlier virtual method table. + */ +wlmtk_surface_vmt_t wlmtk_surface_extend( + wlmtk_surface_t *surface_ptr, + const wlmtk_surface_vmt_t *surface_vmt_ptr); + +/** + * Returns a pointer to the surface's element superclass instance. + * + * @param surface_ptr + * + * @return Pointer to the corresponding @ref wlmtk_element_t. + */ +wlmtk_element_t *wlmtk_surface_element(wlmtk_surface_t *surface_ptr); + +/** + * Virtual method: Request a new size and height for the surface. + * + * Wraps to @ref wlmtk_surface_vmt_t::request_size. + * + * @param surface_ptr + * @param width + * @param height + */ +static inline uint32_t wlmtk_surface_request_size( + wlmtk_surface_t *surface_ptr, + int width, + int height) +{ + return surface_ptr->vmt.request_size(surface_ptr, width, height); +} + +/** Wraps to @ref wlmtk_surface_vmt_t::request_close. */ +static inline void wlmtk_surface_request_close(wlmtk_surface_t *surface_ptr) +{ + surface_ptr->vmt.request_close(surface_ptr); +} + +/** Wraps to @ref wlmtk_surface_vmt_t::set_activated. */ +static inline void wlmtk_surface_set_activated( + wlmtk_surface_t *surface_ptr, + bool activated) +{ + surface_ptr->vmt.set_activated(surface_ptr, activated); +} + +/** + * Returns committed size of the surface. + * + * @param surface_ptr + * @param width_ptr + * @param height_ptr + */ +void wlmtk_surface_get_size( + wlmtk_surface_t *surface_ptr, + int *width_ptr, + int *height_ptr); + +/** + * Commits the given dimensions for the surface. + * + * @param surface_ptr + * @param serial + * @param width + * @param height + */ +void wlmtk_surface_commit_size( + wlmtk_surface_t *surface_ptr, + uint32_t serial, + int width, + int height); + +/** Unit test cases. */ +extern const bs_test_case_t wlmtk_surface_test_cases[]; + +/** Fake surface, useful for unit test. */ +struct _wlmtk_fake_surface_t { + /** Superclass: surface. */ + wlmtk_surface_t surface; + + /** Serial to return on next request_size call. */ + uint32_t serial; + /** `width` argument eof last @ref wlmtk_surface_request_size call. */ + int requested_width; + /** `height` argument of last @ref wlmtk_surface_request_size call. */ + int requested_height; + + /** Whether @ref wlmtk_surface_request_close was called. */ + bool request_close_called; + /** Argument of last @ref wlmtk_surface_set_activated call. */ + bool activated; +}; + +/** Ctor for the fake surface.*/ +wlmtk_fake_surface_t *wlmtk_fake_surface_create(void); + +/** Dtor for the fake surface.*/ +void wlmtk_fake_surface_destroy(wlmtk_fake_surface_t *fake_surface_ptr); + +/** Commits the earlier @ref wlmtk_surface_request_size data. */ +void wlmtk_fake_surface_commit(wlmtk_fake_surface_t *fake_surface_ptr); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_SURFACE_H__ */ +/* == End of surface.h ===================================================== */ diff --git a/src/toolkit/titlebar.c b/src/toolkit/titlebar.c new file mode 100644 index 00000000..f0d4f7fb --- /dev/null +++ b/src/toolkit/titlebar.c @@ -0,0 +1,459 @@ +/* ========================================================================= */ +/** + * @file titlebar.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "titlebar.h" + +#include "box.h" +#include "button.h" +#include "buffer.h" +#include "gfxbuf.h" +#include "primitives.h" +#include "titlebar_button.h" +#include "titlebar_title.h" +#include "window.h" + +#define WLR_USE_UNSTABLE +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +/** State of the title bar. */ +struct _wlmtk_titlebar_t { + /** Superclass: Box. */ + wlmtk_box_t super_box; + /** Link to the titlebar's title. */ + const char *title_ptr; + + /** Title element of the title bar. */ + wlmtk_titlebar_title_t *titlebar_title_ptr; + + /** Minimize button. */ + wlmtk_titlebar_button_t *minimize_button_ptr; + /** Close button. */ + wlmtk_titlebar_button_t *close_button_ptr; + + /** Titlebar background, when focussed. */ + bs_gfxbuf_t *focussed_gfxbuf_ptr; + /** Titlebar background, when blurred. */ + bs_gfxbuf_t *blurred_gfxbuf_ptr; + + /** Current width of the title bar. */ + unsigned width; + /** Position of the close button. */ + int close_position; + /** Position of the title element. */ + int title_position; + /** Width of the title element. */ + int title_width; + /** Whether the title bar is currently displayed as activated. */ + bool activated; + + /** Title bar style. */ + wlmtk_titlebar_style_t style; +}; + +static void _wlmtk_titlebar_element_destroy(wlmtk_element_t *element_ptr); +static bool redraw_buffers( + wlmtk_titlebar_t *titlebar_ptr, + unsigned width); +static bool redraw(wlmtk_titlebar_t *titlebar_ptr); + +/* == Data ================================================================= */ + +/** Virtual method table extension for the titlebar's element superclass. */ +static const wlmtk_element_vmt_t titlebar_element_vmt = { + .destroy = _wlmtk_titlebar_element_destroy +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +wlmtk_titlebar_t *wlmtk_titlebar_create( + wlmtk_env_t *env_ptr, + wlmtk_window_t *window_ptr, + const wlmtk_titlebar_style_t *style_ptr) +{ + wlmtk_titlebar_t *titlebar_ptr = logged_calloc( + 1, sizeof(wlmtk_titlebar_t)); + if (NULL == titlebar_ptr) return NULL; + memcpy(&titlebar_ptr->style, style_ptr, sizeof(wlmtk_titlebar_style_t)); + titlebar_ptr->title_ptr = wlmtk_window_get_title(window_ptr); + + if (!wlmtk_box_init(&titlebar_ptr->super_box, env_ptr, + WLMTK_BOX_HORIZONTAL, + &titlebar_ptr->style.margin_style)) { + wlmtk_titlebar_destroy(titlebar_ptr); + return NULL; + } + wlmtk_element_extend( + &titlebar_ptr->super_box.super_container.super_element, + &titlebar_element_vmt); + + titlebar_ptr->titlebar_title_ptr = wlmtk_titlebar_title_create( + env_ptr, window_ptr); + if (NULL == titlebar_ptr->titlebar_title_ptr) { + wlmtk_titlebar_destroy(titlebar_ptr); + return NULL; + } + wlmtk_box_add_element_front( + &titlebar_ptr->super_box, + wlmtk_titlebar_title_element(titlebar_ptr->titlebar_title_ptr)); + + titlebar_ptr->minimize_button_ptr = wlmtk_titlebar_button_create( + env_ptr, + wlmtk_window_request_minimize, + window_ptr, + wlmaker_primitives_draw_minimize_icon); + if (NULL == titlebar_ptr->minimize_button_ptr) { + wlmtk_titlebar_destroy(titlebar_ptr); + return NULL; + } + wlmtk_box_add_element_front( + &titlebar_ptr->super_box, + wlmtk_titlebar_button_element(titlebar_ptr->minimize_button_ptr)); + + titlebar_ptr->close_button_ptr = wlmtk_titlebar_button_create( + env_ptr, + wlmtk_window_request_close, + window_ptr, + wlmaker_primitives_draw_close_icon); + if (NULL == titlebar_ptr->close_button_ptr) { + wlmtk_titlebar_destroy(titlebar_ptr); + return NULL; + } + wlmtk_box_add_element_back( + &titlebar_ptr->super_box, + wlmtk_titlebar_button_element(titlebar_ptr->close_button_ptr)); + + return titlebar_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_titlebar_destroy(wlmtk_titlebar_t *titlebar_ptr) +{ + if (NULL != titlebar_ptr->close_button_ptr) { + wlmtk_box_remove_element( + &titlebar_ptr->super_box, + wlmtk_titlebar_button_element(titlebar_ptr->close_button_ptr)); + wlmtk_titlebar_button_destroy(titlebar_ptr->close_button_ptr); + titlebar_ptr->close_button_ptr = NULL; + } + + if (NULL != titlebar_ptr->minimize_button_ptr) { + wlmtk_box_remove_element( + &titlebar_ptr->super_box, + wlmtk_titlebar_button_element(titlebar_ptr->minimize_button_ptr)); + wlmtk_titlebar_button_destroy(titlebar_ptr->minimize_button_ptr); + titlebar_ptr->minimize_button_ptr = NULL; + } + + if (NULL != titlebar_ptr->titlebar_title_ptr) { + wlmtk_box_remove_element( + &titlebar_ptr->super_box, + wlmtk_titlebar_title_element(titlebar_ptr->titlebar_title_ptr)); + wlmtk_titlebar_title_destroy(titlebar_ptr->titlebar_title_ptr); + titlebar_ptr->titlebar_title_ptr = NULL; + } + + if (NULL != titlebar_ptr->blurred_gfxbuf_ptr) { + bs_gfxbuf_destroy(titlebar_ptr->blurred_gfxbuf_ptr); + titlebar_ptr->blurred_gfxbuf_ptr = NULL; + } + if (NULL != titlebar_ptr->focussed_gfxbuf_ptr) { + bs_gfxbuf_destroy(titlebar_ptr->focussed_gfxbuf_ptr); + titlebar_ptr->focussed_gfxbuf_ptr = NULL; + } + + wlmtk_box_fini(&titlebar_ptr->super_box); + + free(titlebar_ptr); +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_titlebar_set_width( + wlmtk_titlebar_t *titlebar_ptr, + unsigned width) +{ + if (titlebar_ptr->width == width) return true; + if (!redraw_buffers(titlebar_ptr, width)) return false; + BS_ASSERT(width == titlebar_ptr->width); + titlebar_ptr->title_width = width; + + // Room for a close button? + titlebar_ptr->close_position = width; + if (3 * titlebar_ptr->style.height < width) { + titlebar_ptr->close_position = width - titlebar_ptr->style.height; + titlebar_ptr->title_width -= titlebar_ptr->style.height + + titlebar_ptr->style.margin_style.width; + } + titlebar_ptr->title_position = 0; + // Also having room for a minimize button? + if (4 * titlebar_ptr->style.height < width) { + titlebar_ptr->title_position = titlebar_ptr->style.height + + titlebar_ptr->style.margin_style.width; + titlebar_ptr->title_width -= titlebar_ptr->style.height + + titlebar_ptr->style.margin_style.width; + } + + if (!redraw(titlebar_ptr)) { + return false; + } + + // Don't forget to re-position the elements. + wlmtk_container_update_layout(&titlebar_ptr->super_box.super_container); + return true; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_titlebar_set_activated( + wlmtk_titlebar_t *titlebar_ptr, + bool activated) +{ + if (titlebar_ptr->activated == activated) return; + titlebar_ptr->activated = activated; + wlmtk_titlebar_button_set_activated( + titlebar_ptr->minimize_button_ptr, titlebar_ptr->activated); + wlmtk_titlebar_title_set_activated( + titlebar_ptr->titlebar_title_ptr, titlebar_ptr->activated); + wlmtk_titlebar_button_set_activated( + titlebar_ptr->close_button_ptr, titlebar_ptr->activated); +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_titlebar_is_activated(wlmtk_titlebar_t *titlebar_ptr) +{ + return titlebar_ptr->activated; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_titlebar_set_title( + wlmtk_titlebar_t *titlebar_ptr, + const char *title_ptr) +{ + if (titlebar_ptr->title_ptr == title_ptr) return; + + titlebar_ptr->title_ptr = title_ptr; + redraw(titlebar_ptr); +} + +/* ------------------------------------------------------------------------- */ +wlmtk_element_t *wlmtk_titlebar_element(wlmtk_titlebar_t *titlebar_ptr) +{ + return &titlebar_ptr->super_box.super_container.super_element; +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** Virtual destructor, wraps to our dtor. */ +void _wlmtk_titlebar_element_destroy(wlmtk_element_t *element_ptr) +{ + wlmtk_titlebar_t *titlebar_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_titlebar_t, + super_box.super_container.super_element); + wlmtk_titlebar_destroy(titlebar_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Redraws the titlebar's background in appropriate size. */ +bool redraw_buffers(wlmtk_titlebar_t *titlebar_ptr, unsigned width) +{ + cairo_t *cairo_ptr; + + bs_gfxbuf_t *focussed_gfxbuf_ptr = bs_gfxbuf_create( + width, titlebar_ptr->style.height); + if (NULL == focussed_gfxbuf_ptr) return false; + cairo_ptr = cairo_create_from_bs_gfxbuf(focussed_gfxbuf_ptr); + if (NULL == cairo_ptr) { + bs_gfxbuf_destroy(focussed_gfxbuf_ptr); + return false; + } + wlmaker_primitives_cairo_fill( + cairo_ptr, &titlebar_ptr->style.focussed_fill); + cairo_destroy(cairo_ptr); + + bs_gfxbuf_t *blurred_gfxbuf_ptr = bs_gfxbuf_create( + width, titlebar_ptr->style.height); + if (NULL == blurred_gfxbuf_ptr) return false; + cairo_ptr = cairo_create_from_bs_gfxbuf(blurred_gfxbuf_ptr); + if (NULL == cairo_ptr) { + bs_gfxbuf_destroy(blurred_gfxbuf_ptr); + bs_gfxbuf_destroy(focussed_gfxbuf_ptr); + return false; + } + wlmaker_primitives_cairo_fill( + cairo_ptr, &titlebar_ptr->style.blurred_fill); + cairo_destroy(cairo_ptr); + + if (NULL != titlebar_ptr->focussed_gfxbuf_ptr) { + bs_gfxbuf_destroy(titlebar_ptr->focussed_gfxbuf_ptr); + } + titlebar_ptr->focussed_gfxbuf_ptr = focussed_gfxbuf_ptr; + if (NULL != titlebar_ptr->blurred_gfxbuf_ptr) { + bs_gfxbuf_destroy(titlebar_ptr->blurred_gfxbuf_ptr); + } + titlebar_ptr->blurred_gfxbuf_ptr = blurred_gfxbuf_ptr; + titlebar_ptr->width = width; + return true; +} + +/* ------------------------------------------------------------------------- */ +/** Redraws the titlebar elements. */ +bool redraw(wlmtk_titlebar_t *titlebar_ptr) +{ + // Guard clause: Nothing to do... yet. + if (0 >= titlebar_ptr->width) return true; + + if (!wlmtk_titlebar_title_redraw( + titlebar_ptr->titlebar_title_ptr, + titlebar_ptr->focussed_gfxbuf_ptr, + titlebar_ptr->blurred_gfxbuf_ptr, + titlebar_ptr->title_position, + titlebar_ptr->title_width, + titlebar_ptr->activated, + titlebar_ptr->title_ptr, + &titlebar_ptr->style)) { + return false; + } + wlmtk_element_set_visible( + wlmtk_titlebar_title_element(titlebar_ptr->titlebar_title_ptr), true); + + if (0 < titlebar_ptr->title_position) { + if (!wlmtk_titlebar_button_redraw( + titlebar_ptr->minimize_button_ptr, + titlebar_ptr->focussed_gfxbuf_ptr, + titlebar_ptr->blurred_gfxbuf_ptr, + 0, + &titlebar_ptr->style)) { + return false; + } + wlmtk_element_set_visible( + wlmtk_titlebar_button_element(titlebar_ptr->minimize_button_ptr), + true); + } else { + wlmtk_element_set_visible( + wlmtk_titlebar_button_element(titlebar_ptr->minimize_button_ptr), + false); + } + + if (titlebar_ptr->close_position < (int)titlebar_ptr->width) { + if (!wlmtk_titlebar_button_redraw( + titlebar_ptr->close_button_ptr, + titlebar_ptr->focussed_gfxbuf_ptr, + titlebar_ptr->blurred_gfxbuf_ptr, + titlebar_ptr->close_position, + &titlebar_ptr->style)) { + return false; + } + wlmtk_element_set_visible( + wlmtk_titlebar_button_element(titlebar_ptr->close_button_ptr), + true); + } else { + wlmtk_element_set_visible( + wlmtk_titlebar_button_element(titlebar_ptr->close_button_ptr), + false); + } + + return true; +} + +/* == Unit tests =========================================================== */ + +static void test_create_destroy(bs_test_t *test_ptr); +static void test_variable_width(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_titlebar_test_cases[] = { + { 1, "create_destroy", test_create_destroy }, + { 1, "variable_width", test_variable_width }, + { 0, NULL, NULL } +}; + +/* ------------------------------------------------------------------------- */ +/** Tests setup and teardown. */ +void test_create_destroy(bs_test_t *test_ptr) +{ + wlmtk_fake_window_t *fake_window_ptr = wlmtk_fake_window_create(); + wlmtk_titlebar_style_t style = {}; + wlmtk_titlebar_t *titlebar_ptr = wlmtk_titlebar_create( + NULL, fake_window_ptr->window_ptr, &style); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, titlebar_ptr); + + wlmtk_element_destroy(wlmtk_titlebar_element(titlebar_ptr)); + wlmtk_fake_window_destroy(fake_window_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests titlebar with variable width. */ +void test_variable_width(bs_test_t *test_ptr) +{ + wlmtk_fake_window_t *fake_window_ptr = wlmtk_fake_window_create(); + wlmtk_titlebar_style_t style = { .height = 22, .margin_style = { .width = 2 } }; + wlmtk_titlebar_t *titlebar_ptr = wlmtk_titlebar_create( + NULL, fake_window_ptr->window_ptr, &style); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, titlebar_ptr); + + // Short names, for improved readability. + wlmtk_element_t *title_elem_ptr = wlmtk_titlebar_title_element( + titlebar_ptr->titlebar_title_ptr); + wlmtk_element_t *minimize_elem_ptr = wlmtk_titlebar_button_element( + titlebar_ptr->minimize_button_ptr); + wlmtk_element_t *close_elem_ptr = wlmtk_titlebar_button_element( + titlebar_ptr->close_button_ptr); + int width; + + // Created with zero width: All invisible. */ + BS_TEST_VERIFY_FALSE(test_ptr, title_elem_ptr->visible); + BS_TEST_VERIFY_FALSE(test_ptr, minimize_elem_ptr->visible); + BS_TEST_VERIFY_FALSE(test_ptr, close_elem_ptr->visible); + + // Width sufficient for all: All elements visible and placed. + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_titlebar_set_width(titlebar_ptr, 89)); + BS_TEST_VERIFY_TRUE(test_ptr, title_elem_ptr->visible); + BS_TEST_VERIFY_TRUE(test_ptr, minimize_elem_ptr->visible); + BS_TEST_VERIFY_TRUE(test_ptr, close_elem_ptr->visible); + BS_TEST_VERIFY_EQ(test_ptr, 24, title_elem_ptr->x); + wlmtk_element_get_dimensions(title_elem_ptr, NULL, NULL, &width, NULL); + BS_TEST_VERIFY_EQ(test_ptr, 41, width); + BS_TEST_VERIFY_EQ(test_ptr, 67, close_elem_ptr->x); + + // Width sufficient only for 1 button. + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_titlebar_set_width(titlebar_ptr, 67)); + BS_TEST_VERIFY_TRUE(test_ptr, title_elem_ptr->visible); + BS_TEST_VERIFY_FALSE(test_ptr, minimize_elem_ptr->visible); + BS_TEST_VERIFY_TRUE(test_ptr, close_elem_ptr->visible); + BS_TEST_VERIFY_EQ(test_ptr, 0, title_elem_ptr->x); + wlmtk_element_get_dimensions(title_elem_ptr, NULL, NULL, &width, NULL); + BS_TEST_VERIFY_EQ(test_ptr, 43, width); + + // Width doesn't permit any button. + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_titlebar_set_width(titlebar_ptr, 66)); + BS_TEST_VERIFY_TRUE(test_ptr, title_elem_ptr->visible); + BS_TEST_VERIFY_FALSE(test_ptr, minimize_elem_ptr->visible); + BS_TEST_VERIFY_FALSE(test_ptr, close_elem_ptr->visible); + BS_TEST_VERIFY_EQ(test_ptr, 0, title_elem_ptr->x); + wlmtk_element_get_dimensions(title_elem_ptr, NULL, NULL, &width, NULL); + BS_TEST_VERIFY_EQ(test_ptr, 66, width); + + wlmtk_element_destroy(wlmtk_titlebar_element(titlebar_ptr)); + wlmtk_fake_window_destroy(fake_window_ptr); +} + +/* == End of titlebar.c ==================================================== */ diff --git a/src/toolkit/titlebar.h b/src/toolkit/titlebar.h new file mode 100644 index 00000000..165b81a8 --- /dev/null +++ b/src/toolkit/titlebar.h @@ -0,0 +1,130 @@ +/* ========================================================================= */ +/** + * @file titlebar.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_TITLEBAR_H__ +#define __WLMTK_TITLEBAR_H__ + +/** Forward declaration: Title bar. */ +typedef struct _wlmtk_titlebar_t wlmtk_titlebar_t; + +#include "element.h" + +#include "primitives.h" +#include "window.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Style options for the titlebar. */ +typedef struct { + /** Fill style for when the titlebar is focussed (activated). */ + wlmtk_style_fill_t focussed_fill; + /** Fill style for when the titlebar is blurred (not activated). */ + wlmtk_style_fill_t blurred_fill; + /** Color of the title text when focussed. */ + uint32_t focussed_text_color; + /** Color of the title text when blurred. */ + uint32_t blurred_text_color; + /** Height of the title bar, in pixels. */ + uint32_t height; + /** Width of the bezel. */ + uint32_t bezel_width; + /** Style of the margin within the resizebar. */ + wlmtk_margin_style_t margin_style; +} wlmtk_titlebar_style_t; + +/** + * Creates a title bar, suitable as a window title. + * + * @param env_ptr + * @param window_ptr + * @param style_ptr + * + * @return Pointer to the title bar state, or NULL on error. Must be free'd + * by calling @ref wlmtk_titlebar_destroy. + */ +wlmtk_titlebar_t *wlmtk_titlebar_create( + wlmtk_env_t *env_ptr, + wlmtk_window_t *window_ptr, + const wlmtk_titlebar_style_t *style_ptr); + +/** + * Destroys the title bar. + * + * @param titlebar_ptr + */ +void wlmtk_titlebar_destroy(wlmtk_titlebar_t *titlebar_ptr); + +/** + * Sets the width of the title bar. + * + * @param titlebar_ptr + * @param width + * + * @return Whether the operation was successful. + */ +bool wlmtk_titlebar_set_width( + wlmtk_titlebar_t *titlebar_ptr, + unsigned width); + +/** + * Sets whether the title bar is activated. + * + * @param titlebar_ptr + * @param activated + */ +void wlmtk_titlebar_set_activated( + wlmtk_titlebar_t *titlebar_ptr, + bool activated); + +/** Returns whether the title bar is activated. */ +bool wlmtk_titlebar_is_activated(wlmtk_titlebar_t *titlebar_ptr); + +/** + * Updates the title text of the titlebar. + * + * @param titlebar_ptr + * @param title_ptr Expected to remain valid until the next call of + * @ref wlmtk_titlebar_set_title or until the + * `titlebar_ptr` is destroyed. + */ +void wlmtk_titlebar_set_title( + wlmtk_titlebar_t *titlebar_ptr, + const char *title_ptr); + +/** + * Returns the super Element of the titlebar. + * + * @param titlebar_ptr + * + * @return Pointer to the @ref wlmtk_element_t base instantiation to + * titlebar_ptr. + */ +wlmtk_element_t *wlmtk_titlebar_element(wlmtk_titlebar_t *titlebar_ptr); + +/** Unit test cases. */ +extern const bs_test_case_t wlmtk_titlebar_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_TITLEBAR_H__ */ +/* == End of titlebar.h ==================================================== */ diff --git a/src/toolkit/titlebar_button.c b/src/toolkit/titlebar_button.c new file mode 100644 index 00000000..99a123ac --- /dev/null +++ b/src/toolkit/titlebar_button.c @@ -0,0 +1,363 @@ +/* ========================================================================= */ +/** + * @file titlebar_button.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "titlebar_button.h" + +#include "button.h" +#include "gfxbuf.h" +#include "primitives.h" + +#define WLR_USE_UNSTABLE +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +/** State of a titlebar button. */ +struct _wlmtk_titlebar_button_t { + /** Superclass: Button. */ + wlmtk_button_t super_button; + /** Whether the titlebar button is activated (focussed). */ + bool activated; + + /** Callback for when the button is clicked. */ + void (*click_handler)(wlmtk_window_t *window_ptr); + /** Points to the @ref wlmtk_window_t that carries this titlebar. */ + wlmtk_window_t *window_ptr; + /** For drawing the button contents. */ + wlmtk_titlebar_button_draw_t draw; + + /** WLR buffer of the button when focussed & released. */ + struct wlr_buffer *focussed_released_wlr_buffer_ptr; + /** WLR buffer of the button when focussed & pressed. */ + struct wlr_buffer *focussed_pressed_wlr_buffer_ptr; + /** WLR buffer of the button when blurred. */ + struct wlr_buffer *blurred_wlr_buffer_ptr; +}; + +static void titlebar_button_element_destroy(wlmtk_element_t *element_ptr); +static void titlebar_button_clicked(wlmtk_button_t *button_ptr); +static void update_buffers(wlmtk_titlebar_button_t *titlebar_button_ptr); +static struct wlr_buffer *create_buf( + bs_gfxbuf_t *gfxbuf_ptr, + int position, + bool pressed, + const wlmtk_titlebar_style_t *style_ptr, + wlmtk_titlebar_button_draw_t draw); + +/* == Data ================================================================= */ + +/** Extension to the superclass element's virtual method table. */ +static const wlmtk_element_vmt_t titlebar_button_element_vmt = { + .destroy = titlebar_button_element_destroy, +}; + +/** Extension to the parent button class' virtual methods. */ +static const wlmtk_button_vmt_t titlebar_button_vmt = { + .clicked = titlebar_button_clicked, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +wlmtk_titlebar_button_t *wlmtk_titlebar_button_create( + wlmtk_env_t *env_ptr, + void (*click_handler)(wlmtk_window_t *window_ptr), + wlmtk_window_t *window_ptr, + wlmtk_titlebar_button_draw_t draw) +{ + BS_ASSERT(NULL != window_ptr); + BS_ASSERT(NULL != click_handler); + BS_ASSERT(NULL != draw); + wlmtk_titlebar_button_t *titlebar_button_ptr = logged_calloc( + 1, sizeof(wlmtk_titlebar_button_t)); + if (NULL == titlebar_button_ptr) return NULL; + titlebar_button_ptr->click_handler = click_handler; + titlebar_button_ptr->window_ptr = window_ptr; + titlebar_button_ptr->draw = draw; + + if (!wlmtk_button_init(&titlebar_button_ptr->super_button, env_ptr)) { + wlmtk_titlebar_button_destroy(titlebar_button_ptr); + return NULL; + } + wlmtk_element_extend( + &titlebar_button_ptr->super_button.super_buffer.super_element, + &titlebar_button_element_vmt); + wlmtk_button_extend( + &titlebar_button_ptr->super_button, + &titlebar_button_vmt); + + return titlebar_button_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_titlebar_button_destroy( + wlmtk_titlebar_button_t *titlebar_button_ptr) +{ + wlr_buffer_drop_nullify( + &titlebar_button_ptr->focussed_released_wlr_buffer_ptr); + wlr_buffer_drop_nullify( + &titlebar_button_ptr->focussed_pressed_wlr_buffer_ptr); + wlr_buffer_drop_nullify( + &titlebar_button_ptr->blurred_wlr_buffer_ptr); + + wlmtk_button_fini(&titlebar_button_ptr->super_button); + free(titlebar_button_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_titlebar_button_set_activated( + wlmtk_titlebar_button_t *titlebar_button_ptr, + bool activated) +{ + if (titlebar_button_ptr->activated == activated) return; + titlebar_button_ptr->activated = activated; + update_buffers(titlebar_button_ptr); +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_titlebar_button_redraw( + wlmtk_titlebar_button_t *titlebar_button_ptr, + bs_gfxbuf_t *focussed_gfxbuf_ptr, + bs_gfxbuf_t *blurred_gfxbuf_ptr, + int position, + const wlmtk_titlebar_style_t *style_ptr) +{ + BS_ASSERT(focussed_gfxbuf_ptr->width == blurred_gfxbuf_ptr->width); + BS_ASSERT(focussed_gfxbuf_ptr->height == blurred_gfxbuf_ptr->height); + BS_ASSERT(style_ptr->height == focussed_gfxbuf_ptr->height); + BS_ASSERT(position + style_ptr->height <= focussed_gfxbuf_ptr->width); + + struct wlr_buffer *focussed_released_ptr = create_buf( + focussed_gfxbuf_ptr, position, false, style_ptr, + titlebar_button_ptr->draw); + struct wlr_buffer *focussed_pressed_ptr = create_buf( + focussed_gfxbuf_ptr, position, true, style_ptr, + titlebar_button_ptr->draw); + struct wlr_buffer *blurred_ptr = create_buf( + blurred_gfxbuf_ptr, position, false, style_ptr, + titlebar_button_ptr->draw); + + if (NULL != focussed_released_ptr && + NULL != focussed_pressed_ptr && + NULL != blurred_ptr) { + wlr_buffer_drop_nullify( + &titlebar_button_ptr->focussed_released_wlr_buffer_ptr); + wlr_buffer_drop_nullify( + &titlebar_button_ptr->focussed_pressed_wlr_buffer_ptr); + wlr_buffer_drop_nullify( + &titlebar_button_ptr->blurred_wlr_buffer_ptr); + + titlebar_button_ptr->focussed_released_wlr_buffer_ptr = + focussed_released_ptr; + titlebar_button_ptr->focussed_pressed_wlr_buffer_ptr = + focussed_pressed_ptr; + titlebar_button_ptr->blurred_wlr_buffer_ptr = blurred_ptr; + + update_buffers(titlebar_button_ptr); + return true; + } + + wlr_buffer_drop_nullify(&focussed_released_ptr); + wlr_buffer_drop_nullify(&focussed_pressed_ptr); + wlr_buffer_drop_nullify(&blurred_ptr); + return false; +} + +/* ------------------------------------------------------------------------- */ +wlmtk_element_t *wlmtk_titlebar_button_element( + wlmtk_titlebar_button_t *titlebar_button_ptr) +{ + return &titlebar_button_ptr->super_button.super_buffer.super_element; +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** Virtual destructor, wraps to @ref wlmtk_titlebar_button_destroy. */ +void titlebar_button_element_destroy(wlmtk_element_t *element_ptr) +{ + wlmtk_titlebar_button_t *titlebar_button_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_titlebar_button_t, + super_button.super_buffer.super_element); + wlmtk_titlebar_button_destroy(titlebar_button_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Handles button clicks: Passes the request to the window. */ +void titlebar_button_clicked(wlmtk_button_t *button_ptr) +{ + wlmtk_titlebar_button_t *titlebar_button_ptr = BS_CONTAINER_OF( + button_ptr, wlmtk_titlebar_button_t, super_button); + titlebar_button_ptr->click_handler(titlebar_button_ptr->window_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Updates the button's buffer depending on activation status. */ +void update_buffers(wlmtk_titlebar_button_t *titlebar_button_ptr) +{ + // No buffer: Nothing to update. + if (NULL == titlebar_button_ptr->focussed_released_wlr_buffer_ptr || + NULL == titlebar_button_ptr->focussed_pressed_wlr_buffer_ptr || + NULL == titlebar_button_ptr->blurred_wlr_buffer_ptr) return; + + if (titlebar_button_ptr->activated) { + wlmtk_button_set( + &titlebar_button_ptr->super_button, + titlebar_button_ptr->focussed_released_wlr_buffer_ptr, + titlebar_button_ptr->focussed_pressed_wlr_buffer_ptr); + } else { + wlmtk_button_set( + &titlebar_button_ptr->super_button, + titlebar_button_ptr->blurred_wlr_buffer_ptr, + titlebar_button_ptr->blurred_wlr_buffer_ptr); + } +} + +/* ------------------------------------------------------------------------- */ +/** Helper: Creates a WLR buffer for the button. */ +struct wlr_buffer *create_buf( + bs_gfxbuf_t *gfxbuf_ptr, + int position, + bool pressed, + const wlmtk_titlebar_style_t *style_ptr, + void (*draw)(cairo_t *cairo_ptr, uint32_t color)) +{ + struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( + style_ptr->height, style_ptr->height); + if (NULL == wlr_buffer_ptr) return NULL; + + bs_gfxbuf_copy_area( + bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr), 0, 0, + gfxbuf_ptr, position, 0, style_ptr->height, style_ptr->height); + + cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); + if (NULL == cairo_ptr) { + wlr_buffer_drop(wlr_buffer_ptr); + return NULL; + } + wlmaker_primitives_draw_bezel( + cairo_ptr, style_ptr->bezel_width, !pressed); + draw(cairo_ptr, style_ptr->focussed_text_color); + cairo_destroy(cairo_ptr); + + return wlr_buffer_ptr; +} + +/* == Unit tests =========================================================== */ + +static void test_button(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_titlebar_button_test_cases[] = { + { 1, "button", test_button }, + { 0, NULL, NULL } +}; + +/* ------------------------------------------------------------------------- */ +/** Tests button visualization. */ +void test_button(bs_test_t *test_ptr) +{ + wlmtk_fake_window_t *fake_window_ptr = wlmtk_fake_window_create(); + wlmtk_titlebar_button_t *button_ptr = wlmtk_titlebar_button_create( + NULL, + wlmtk_window_request_close, + fake_window_ptr->window_ptr, + wlmaker_primitives_draw_close_icon); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, button_ptr); + wlmtk_titlebar_button_set_activated(button_ptr, true); + + // For improved readability. + wlmtk_buffer_t *super_buffer_ptr = &button_ptr->super_button.super_buffer; + wlmtk_element_t *element_ptr = wlmtk_titlebar_button_element(button_ptr); + + // Draw contents. + wlmtk_titlebar_style_t style = { + .height = 22, + .focussed_text_color = 0xffffffff, + .bezel_width = 1 + }; + bs_gfxbuf_t *f_ptr = bs_gfxbuf_create(100, 22); + bs_gfxbuf_clear(f_ptr, 0xff4040c0); + bs_gfxbuf_t *b_ptr = bs_gfxbuf_create(100, 22); + bs_gfxbuf_clear(b_ptr, 0xff303030); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_titlebar_button_redraw(button_ptr, f_ptr, b_ptr, 30, &style)); + bs_gfxbuf_destroy(b_ptr); + bs_gfxbuf_destroy(f_ptr); + BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( + test_ptr, + bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), + "toolkit/title_button_focussed_released.png"); + + // Pointer must be inside the button for accepting DOWN. + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(element_ptr, 11, 11, 0)); + + // Button down: pressed. + wlmtk_button_event_t button = { + .button = BTN_LEFT, .type = WLMTK_BUTTON_DOWN + }; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(element_ptr, &button)); + BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( + test_ptr, + bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), + "toolkit/title_button_focussed_pressed.png"); + + button.type = WLMTK_BUTTON_UP; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(element_ptr, &button)); + BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( + test_ptr, + bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), + "toolkit/title_button_focussed_released.png"); + + // Click: To be passed along, no change to visual. + BS_TEST_VERIFY_FALSE( + test_ptr, + fake_window_ptr->fake_surface_ptr->request_close_called); + button.type = WLMTK_BUTTON_CLICK; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(element_ptr, &button)); + BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( + test_ptr, + bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), + "toolkit/title_button_focussed_released.png"); + BS_TEST_VERIFY_TRUE( + test_ptr, + fake_window_ptr->fake_surface_ptr->request_close_called); + + // De-activate: Show as blurred. + wlmtk_titlebar_button_set_activated(button_ptr, false); + BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( + test_ptr, + bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), + "toolkit/title_button_blurred.png"); + + wlmtk_element_destroy(element_ptr); + wlmtk_fake_window_destroy(fake_window_ptr); +} + +/* == End of titlebar_button.c ============================================= */ diff --git a/src/toolkit/titlebar_button.h b/src/toolkit/titlebar_button.h new file mode 100644 index 00000000..83501f00 --- /dev/null +++ b/src/toolkit/titlebar_button.h @@ -0,0 +1,109 @@ +/* ========================================================================= */ +/** + * @file titlebar_button.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_TITLEBAR_BUTTON_H__ +#define __WLMTK_TITLEBAR_BUTTON_H__ + +#include +#include + +/** Forward declaration. */ +typedef struct _wlmtk_titlebar_button_t wlmtk_titlebar_button_t; + +#include "titlebar.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Function pointer to method for drawing the button contents. */ +typedef void (*wlmtk_titlebar_button_draw_t)( + cairo_t *cairo_ptr, uint32_t color); + +/** + * Creates a button for the titlebar. + * + * @param env_ptr + * @param click_handler + * @param window_ptr + * @param draw + * + * @return Pointer to the titlebar button, or NULL on error. + */ +wlmtk_titlebar_button_t *wlmtk_titlebar_button_create( + wlmtk_env_t *env_ptr, + void (*click_handler)(wlmtk_window_t *window_ptr), + wlmtk_window_t *window_ptr, + wlmtk_titlebar_button_draw_t draw); + +/** + * Destroys the titlebar button. + * + * @param titlebar_button_ptr + */ +void wlmtk_titlebar_button_destroy( + wlmtk_titlebar_button_t *titlebar_button_ptr); + +/** + * Sets the activation status (focussed / blurred) of the titlebar button. + * + * @param titlebar_button_ptr + * @param activated + */ +void wlmtk_titlebar_button_set_activated( + wlmtk_titlebar_button_t *titlebar_button_ptr, + bool activated); + +/** + * Redraws the titlebar button for given textures, position and style. + * + * @param titlebar_button_ptr + * @param focussed_gfxbuf_ptr + * @param blurred_gfxbuf_ptr + * @param position + * @param style_ptr + * + * @return true on success. + */ +bool wlmtk_titlebar_button_redraw( + wlmtk_titlebar_button_t *titlebar_button_ptr, + bs_gfxbuf_t *focussed_gfxbuf_ptr, + bs_gfxbuf_t *blurred_gfxbuf_ptr, + int position, + const wlmtk_titlebar_style_t *style_ptr); + +/** + * Returns the titlebar button's super element. + * + * @param titlebar_button_ptr + * + * @return Pointer to the superclass @ref wlmtk_element_t. + */ +wlmtk_element_t *wlmtk_titlebar_button_element( + wlmtk_titlebar_button_t *titlebar_button_ptr); + +/** Unit test cases. */ +extern const bs_test_case_t wlmtk_titlebar_button_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_TITLEBAR_BUTTON_H__ */ +/* == End of titlebar_button.h ============================================= */ diff --git a/src/toolkit/titlebar_title.c b/src/toolkit/titlebar_title.c new file mode 100644 index 00000000..4ebe4418 --- /dev/null +++ b/src/toolkit/titlebar_title.c @@ -0,0 +1,346 @@ +/* ========================================================================= */ +/** + * @file titlebar_title.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "titlebar_title.h" + +#include "buffer.h" +#include "gfxbuf.h" +#include "primitives.h" +#include "window.h" + +#define WLR_USE_UNSTABLE +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +/** State of the title bar's title. */ +struct _wlmtk_titlebar_title_t { + /** Superclass: Buffer. */ + wlmtk_buffer_t super_buffer; + /** Pointer to the window the title element belongs to. */ + wlmtk_window_t *window_ptr; + + /** The drawn title, when focussed. */ + struct wlr_buffer *focussed_wlr_buffer_ptr; + /** The drawn title, when blurred. */ + struct wlr_buffer *blurred_wlr_buffer_ptr; +}; + +static void _wlmtk_titlebar_title_element_destroy( + wlmtk_element_t *element_ptr); +static bool _wlmtk_titlebar_title_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr); + +static void title_set_activated( + wlmtk_titlebar_title_t *titlebar_title_ptr, + bool activated); +struct wlr_buffer *title_create_buffer( + bs_gfxbuf_t *gfxbuf_ptr, + unsigned position, + unsigned width, + uint32_t text_color, + const char *title_ptr, + const wlmtk_titlebar_style_t *style_ptr); + +/* == Data ================================================================= */ + +/** Extension to the superclass elment's virtual method table. */ +static const wlmtk_element_vmt_t titlebar_title_element_vmt = { + .destroy = _wlmtk_titlebar_title_element_destroy, + .pointer_button = _wlmtk_titlebar_title_element_pointer_button, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +wlmtk_titlebar_title_t *wlmtk_titlebar_title_create( + wlmtk_env_t *env_ptr, + wlmtk_window_t *window_ptr) +{ + wlmtk_titlebar_title_t *titlebar_title_ptr = logged_calloc( + 1, sizeof(wlmtk_titlebar_title_t)); + if (NULL == titlebar_title_ptr) return NULL; + titlebar_title_ptr->window_ptr = window_ptr; + + if (!wlmtk_buffer_init(&titlebar_title_ptr->super_buffer, env_ptr)) { + wlmtk_titlebar_title_destroy(titlebar_title_ptr); + return NULL; + } + wlmtk_element_extend( + &titlebar_title_ptr->super_buffer.super_element, + &titlebar_title_element_vmt); + + return titlebar_title_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_titlebar_title_destroy(wlmtk_titlebar_title_t *titlebar_title_ptr) +{ + wlr_buffer_drop_nullify(&titlebar_title_ptr->focussed_wlr_buffer_ptr); + wlr_buffer_drop_nullify(&titlebar_title_ptr->blurred_wlr_buffer_ptr); + wlmtk_buffer_fini(&titlebar_title_ptr->super_buffer); + free(titlebar_title_ptr); +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_titlebar_title_redraw( + wlmtk_titlebar_title_t *titlebar_title_ptr, + bs_gfxbuf_t *focussed_gfxbuf_ptr, + bs_gfxbuf_t *blurred_gfxbuf_ptr, + int position, + int width, + bool activated, + const char *title_ptr, + const wlmtk_titlebar_style_t *style_ptr) +{ + BS_ASSERT(focussed_gfxbuf_ptr->width == blurred_gfxbuf_ptr->width); + BS_ASSERT(style_ptr->height == focussed_gfxbuf_ptr->height); + BS_ASSERT(style_ptr->height == blurred_gfxbuf_ptr->height); + BS_ASSERT(position <= (int)focussed_gfxbuf_ptr->width); + BS_ASSERT(position + width <= (int)focussed_gfxbuf_ptr->width); + + if (NULL == title_ptr) title_ptr = ""; + + struct wlr_buffer *focussed_wlr_buffer_ptr = title_create_buffer( + focussed_gfxbuf_ptr, position, width, + style_ptr->focussed_text_color, title_ptr, style_ptr); + struct wlr_buffer *blurred_wlr_buffer_ptr = title_create_buffer( + blurred_gfxbuf_ptr, position, width, + style_ptr->blurred_text_color, title_ptr, style_ptr); + + if (NULL == focussed_wlr_buffer_ptr || + NULL == blurred_wlr_buffer_ptr) { + wlr_buffer_drop_nullify(&focussed_wlr_buffer_ptr); + wlr_buffer_drop_nullify(&blurred_wlr_buffer_ptr); + return false; + } + + wlr_buffer_drop_nullify(&titlebar_title_ptr->focussed_wlr_buffer_ptr); + titlebar_title_ptr->focussed_wlr_buffer_ptr = focussed_wlr_buffer_ptr; + wlr_buffer_drop_nullify(&titlebar_title_ptr->blurred_wlr_buffer_ptr); + titlebar_title_ptr->blurred_wlr_buffer_ptr = blurred_wlr_buffer_ptr; + + title_set_activated(titlebar_title_ptr, activated); + return true; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_titlebar_title_set_activated( + wlmtk_titlebar_title_t *titlebar_title_ptr, + bool activated) +{ + title_set_activated(titlebar_title_ptr, activated); +} + +/* ------------------------------------------------------------------------- */ +wlmtk_element_t *wlmtk_titlebar_title_element( + wlmtk_titlebar_title_t *titlebar_title_ptr) +{ + return &titlebar_title_ptr->super_buffer.super_element; +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** Dtor. */ +void _wlmtk_titlebar_title_element_destroy( + wlmtk_element_t *element_ptr) +{ + wlmtk_titlebar_title_t *titlebar_title_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_titlebar_title_t, super_buffer.super_element); + wlmtk_titlebar_title_destroy(titlebar_title_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** See @ref wlmtk_element_vmt_t::pointer_button. */ +bool _wlmtk_titlebar_title_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr) +{ + wlmtk_titlebar_title_t *titlebar_title_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_titlebar_title_t, super_buffer.super_element); + + if (button_event_ptr->button != BTN_LEFT) return false; + + switch (button_event_ptr->type) { + case WLMTK_BUTTON_DOWN: + wlmtk_window_request_move(titlebar_title_ptr->window_ptr); + break; + + default: // Can be ignored. + break; + } + + return true; + +} + +/* ------------------------------------------------------------------------- */ +/** + * Sets whether the title is drawn focussed (activated) or blurred. + * + * @param titlebar_title_ptr + * @param activated + */ +void title_set_activated( + wlmtk_titlebar_title_t *titlebar_title_ptr, + bool activated) +{ + wlmtk_buffer_set( + &titlebar_title_ptr->super_buffer, + activated ? + titlebar_title_ptr->focussed_wlr_buffer_ptr : + titlebar_title_ptr->blurred_wlr_buffer_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Creates a WLR buffer with the title's texture, as specified. + * + * @param gfxbuf_ptr + * @param position + * @param width + * @param text_color + * @param title_ptr + * @param style_ptr + * + * @return A pointer to a `struct wlr_buffer` with the texture. + */ +struct wlr_buffer *title_create_buffer( + bs_gfxbuf_t *gfxbuf_ptr, + unsigned position, + unsigned width, + uint32_t text_color, + const char *title_ptr, + const wlmtk_titlebar_style_t *style_ptr) +{ + BS_ASSERT(NULL != title_ptr); + struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( + width, style_ptr->height); + if (NULL == wlr_buffer_ptr) return NULL; + + bs_gfxbuf_copy_area( + bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr), + 0, 0, + gfxbuf_ptr, + position, 0, + width, style_ptr->height); + + cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); + if (NULL == cairo_ptr) { + wlr_buffer_drop(wlr_buffer_ptr); + return NULL; + } + wlmaker_primitives_draw_bezel_at( + cairo_ptr, 0, 0, width, style_ptr->height, 1.0, true); + wlmaker_primitives_draw_window_title( + cairo_ptr, title_ptr, text_color); + cairo_destroy(cairo_ptr); + + return wlr_buffer_ptr; +} + +/* == Unit tests =========================================================== */ + +static void test_title(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_titlebar_title_test_cases[] = { + { 1, "title", test_title }, + { 0, NULL, NULL } +}; + +/* ------------------------------------------------------------------------- */ +/** Tests title drawing. */ +void test_title(bs_test_t *test_ptr) +{ + const wlmtk_titlebar_style_t style = { + .focussed_text_color = 0xffc0c0c0, + .blurred_text_color = 0xff808080, + .height = 22, + }; + bs_gfxbuf_t *focussed_gfxbuf_ptr = bs_gfxbuf_create(120, 22); + bs_gfxbuf_t *blurred_gfxbuf_ptr = bs_gfxbuf_create(120, 22); + bs_gfxbuf_clear(focussed_gfxbuf_ptr, 0xff2020c0); + bs_gfxbuf_clear(blurred_gfxbuf_ptr, 0xff404040); + + wlmtk_fake_window_t *fake_window_ptr = wlmtk_fake_window_create(); + wlmtk_titlebar_title_t *titlebar_title_ptr = wlmtk_titlebar_title_create( + NULL, fake_window_ptr->window_ptr); + wlmtk_element_t *element_ptr = wlmtk_titlebar_title_element( + titlebar_title_ptr); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, titlebar_title_ptr); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_titlebar_title_redraw( + titlebar_title_ptr, + focussed_gfxbuf_ptr, blurred_gfxbuf_ptr, + 10, 90, true, "Title", &style)); + + BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( + test_ptr, + bs_gfxbuf_from_wlr_buffer(titlebar_title_ptr->focussed_wlr_buffer_ptr), + "toolkit/title_focussed.png"); + BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( + test_ptr, + bs_gfxbuf_from_wlr_buffer(titlebar_title_ptr->blurred_wlr_buffer_ptr), + "toolkit/title_blurred.png"); + + // We had started as "activated", verify that's correct. + wlmtk_buffer_t *super_buffer_ptr = &titlebar_title_ptr->super_buffer; + BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( + test_ptr, + bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), + "toolkit/title_focussed.png"); + + // De-activated the title. Verify that was propagated. + title_set_activated(titlebar_title_ptr, false); + BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( + test_ptr, + bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), + "toolkit/title_blurred.png"); + + // Redraw with shorter width. Verify that's still correct. + wlmtk_titlebar_title_redraw( + titlebar_title_ptr, focussed_gfxbuf_ptr, blurred_gfxbuf_ptr, + 10, 70, false, "Title", &style); + BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( + test_ptr, + bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), + "toolkit/title_blurred_short.png"); + + // Pressing a button should trigger a move. + BS_TEST_VERIFY_FALSE(test_ptr, fake_window_ptr->request_move_called); + wlmtk_button_event_t button = { + .button = BTN_LEFT, .type = WLMTK_BUTTON_DOWN + }; + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_button(element_ptr, &button)); + BS_TEST_VERIFY_TRUE(test_ptr, fake_window_ptr->request_move_called); + + wlmtk_element_destroy(element_ptr); + wlmtk_fake_window_destroy(fake_window_ptr); + bs_gfxbuf_destroy(focussed_gfxbuf_ptr); + bs_gfxbuf_destroy(blurred_gfxbuf_ptr); +} + +/* == End of titlebar_title.c ============================================== */ diff --git a/src/toolkit/titlebar_title.h b/src/toolkit/titlebar_title.h new file mode 100644 index 00000000..58b82820 --- /dev/null +++ b/src/toolkit/titlebar_title.h @@ -0,0 +1,107 @@ +/* ========================================================================= */ +/** + * @file titlebar_title.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_TITLEBAR_TITLE_H__ +#define __WLMTK_TITLEBAR_TITLE_H__ + +/** Forward declaration. */ +typedef struct _wlmtk_titlebar_title_t wlmtk_titlebar_title_t; + +#include +#include + +#include "titlebar.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Creates a title bar title. + * + * @param env_ptr + * @param window_ptr + * + * @return Title handle. + */ +wlmtk_titlebar_title_t *wlmtk_titlebar_title_create( + wlmtk_env_t *env_ptr, + wlmtk_window_t *window_ptr); + +/** + * Destroys the titlebar title. + * + * @param titlebar_title_ptr + */ +void wlmtk_titlebar_title_destroy( + wlmtk_titlebar_title_t *titlebar_title_ptr); + +/** + * Redraws the title section of the title bar. + * + * @param titlebar_title_ptr + * @param focussed_gfxbuf_ptr Titlebar background when focussed. + * @param blurred_gfxbuf_ptr Titlebar background when blurred. + * @param position Position of title telative to titlebar. + * @param width Width of title. + * @param activated Whether the title bar should start focussed. + * @param title_ptr Title, or NULL. + * @param style_ptr + * + * @return true on success. + */ +bool wlmtk_titlebar_title_redraw( + wlmtk_titlebar_title_t *titlebar_title_ptr, + bs_gfxbuf_t *focussed_gfxbuf_ptr, + bs_gfxbuf_t *blurred_gfxbuf_ptr, + int position, + int width, + bool activated, + const char *title_ptr, + const wlmtk_titlebar_style_t *style_ptr); + +/** + * Sets activation status of the titlebar's title. + * + * @param titlebar_title_ptr + * @param activated + */ +void wlmtk_titlebar_title_set_activated( + wlmtk_titlebar_title_t *titlebar_title_ptr, + bool activated); + +/** + * Returns the superclass @ref wlmtk_element_t for the titlebar title. + * + * @param titlebar_title_ptr + * + * @return Pointer to the super element. + */ +wlmtk_element_t *wlmtk_titlebar_title_element( + wlmtk_titlebar_title_t *titlebar_title_ptr); + +/** Unit test cases. */ +extern const bs_test_case_t wlmtk_titlebar_title_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_TITLEBAR_TITLE_H__ */ +/* == End of titlebar_title.h ============================================== */ diff --git a/src/toolkit/toolkit.h b/src/toolkit/toolkit.h index b9216a39..a150fe88 100644 --- a/src/toolkit/toolkit.h +++ b/src/toolkit/toolkit.h @@ -2,6 +2,8 @@ /** * @file toolkit.h * + * See @ref toolkit_page for documentation. + * * @copyright * Copyright 2023 Google LLC * @@ -17,22 +19,46 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef __TOOLKIT_H__ -#define __TOOLKIT_H__ +#ifndef __WLMTK_TOOLKIT_H__ +#define __WLMTK_TOOLKIT_H__ #include "gfxbuf.h" #include "primitives.h" #include "style.h" +#include "util.h" + +#include +#include + +#include "bordered.h" +#include "box.h" +#include "buffer.h" +#include "button.h" +#include "container.h" +#include "content.h" +#include "element.h" +#include "env.h" +#include "fsm.h" +#include "input.h" +#include "surface.h" +#include "rectangle.h" +#include "resizebar.h" +#include "resizebar_area.h" +#include "titlebar.h" +#include "titlebar_button.h" +#include "titlebar_title.h" +#include "util.h" +#include "window.h" +#include "workspace.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus - #ifdef __cplusplus } // extern "C" #endif // __cplusplus -#endif /* __TOOLKIT_H__ */ +#endif /* __WLMTK_TOOLKIT_H__ */ /* == End of toolkit.h ===================================================== */ diff --git a/src/toolkit/toolkit.md b/src/toolkit/toolkit.md new file mode 100644 index 00000000..c9b4c8a9 --- /dev/null +++ b/src/toolkit/toolkit.md @@ -0,0 +1,397 @@ + +# Toolkit {#toolkit_page} + +## Compositor Elements + +### Class Hierarchy + + +* Where do we use composition, vs. inheritance? + +```plantuml +class Element { + int x, y + struct wlr_scene_node *node_ptr + Container *parent_container_ptr + + bool init(handlers) + bool init_attached(handlers, struct wlr_scene_tree *) + void fini() + -void set_parent_container(Container*) + -void attach_to_scene_graph() + void set_visible(bool) + bool is_visible() + void set_position(int, int) + void get_position(int*, int*) + void get_size(int*, int*) + + {abstract}#void destroy() + {abstract}#struct wlr_scene_node *create_scene_node(parent_node*) + #void pointer_motion(double, double) + #void pointer_button(wlmtk_button_event_t) + #void pointer_leave() +} +note right of Element::"set_parent_container(Container*)" + Will invoke set_parent_container. +end note +note right of Element::"attach_to_scene_graph()" + Will create or reparent this element's node to the parent's scene tree, or + detach and destroy this element's node, if the parent does not have a scene + tree. +end note + +class Container { + Element super_element + Element elements[] + + bool init(handlers) + void fini() + add_element(Element*) + remove_element(Element*) + -struct wlr_scene_tree *wlr_scene_tree() + + {abstract}#void destroy() + #void configure() +} +Element <|-- Container +note right of Element::"add_element(Element*)" + Both add_element() and remove_element() will call + Element::set_parent_container() and thus alignt he element's scene node + with the container's tree. +end note + +class Workspace { + Container super_container + Container layers[] + + Container *create() + void destroy() + + map_window(Window*) + unmap_window(Window*) + + activate_window(Window*) + begin_window_move(Window*) + + map_layer_element(LayerElement *, layer) + unmap_layer_element(LayerElement *, layer) +} +Container *-- Workspace + +class Box { + Container super_container + + bool init(handlers, wlmtk_box_orientation_t) +} +Container <|-- Box + + + +abstract class Surface { + Element super_element + + request_size() + get_size() +} + + +abstract class Content { + Container super_container + + Surface surface + Surface popups[] + + init(surface) + fini() + + request_size() + get_size() + + request_close() + set_activated() +} + + + + +class Toplevel { + Content super_content + + -- because: implement request_close, set_activated, ... + + -- but: Window ? +} + +class Popup { + Surface super_surface +} + + + +abstract class Content { + Element super_element + + init(handlers) + fini() + struct wlr_scene_node *create_scene_node() + Element *element() + -set_window(Window*) + + {abstract}#void get_size(int *, int *) + {abstract}#void set_size(int, int) + {abstract}#void set_activated(bool) + {abstract}#void set_maximized(bool) + {abstract}#void set_fullscreen(bool) +} +Element <|-- Content +note right of Content + Interface for Window contents. + A surface (or... buffer? ...). Ultimately wraps a node, + thus may be an element. +end note + +class LayerElement { + Element parent + + {abstract}#configure() + } +Element <|-- LayerElement + +class LayerShell { +} +LayerElement <|-- LayerShell + +class XdgToplevelSurface { +} +Content <|-- XdgToplevelSurface + +class Buffer { + Element parent + + init(handlers, struct wlr_buffer *) + set(struct wlr_buffer *) +} +Element <|-- Buffer + +class Button { + Buffer super_buffer + + init(handlers, texture_up, texture_down, texture_blurred) + update(texture_up, texture_down, texture_blurred) +} +Buffer <|-- Button + +class Window { + Box super_box + Content *content + TitleBar *title_bar + + Window *create(Content*) + destroy() + Element *element() + + set_activated(bool) + set_server_side_decorated(bool) + get_size(int *, int *) + set_size(int, int) +} +Box *-- Window + +class TitleBar { + Box super_box +} +Box *-- TitleBar + +class TitleBarButton { + Button super_button + + init(handlers, overlay_texture, texture, posx, posy) + redraw(texture, posx, posy) + + get_width(int *) + set_width(int) +} + +class Menu { + Box super_box +} +Box *-- Menu + +class MenuItem { + +} +Buffer <|-- MenuItem + +class Cursor { + Cursor *create() + void destroy() + + attach_input_device(struct wlr_input_device*) + set_image(const char * +} +``` + +### Pending work + +* Separate the "map" method into "attach_to_node" and "set_visible". Elements + should be marked as visible even if their parent is not "mapped" yet; thus + leading to lazy instantiation of the node, once their parent gets "mapped" + (ie. attached to the scene graph). + +### User Journeys + +#### Creating a new XDG toplevel + +* xdg_toplevel... => on handle_new_surface + + * XdgToplevelSurface::create(wlr surface) + * listeners for map, unmap, destroy + + => so yes, what will this do when mapped? + + * Window::create(surface) + * registers the window for workspace + + * creates the container, with parent of window element + * if decoration: + + + * will setup listeners for the various events, ... + * request maximize + * request move + * request show window menu + * set title + * ... + + + set title handler: + + * window::set_title + + request maximize handler: + * window::request_maximize + * window::set_maximized + * internally: get view from workspace, ... set_size + * callback to surface (if set): set_maximized + + + upon surface::map + + * workspace::add_window(window) (unsure: do we need this?) + => should set "container" of window parent... element to workspace::container + (ie. set_parent(...); and add "element" to "container") + + * workspace::map_window(window) + => this should add window to the set of workspace::mapped_windows + => window element->container -> map_element(element) + (expects the container to be mapped) + + => will call map(node?) on window element + - is implemented in Container: + - create a scene tree (from parents node) oc reparent (from parent) + - calls map for every item in container + + upon surface::unmap + * workspace::unmap_window + + => window element->container -> unmap_element(element) + => will call unmap() on window element + => destroy the node + + * workspace::remove_window(window) (do we need this?) + + +There is a click ("pointer button event") -> goes to workspace. + + * use node lookup -> should give data -> element + * element::click(...) + * Button::click gets called. Has a "button_from_element" & goes from there. + + +Button is pressed => pass down to pointer-focussed element. + Would eg. show the "pressed" state of a button, but not activate. + + button_down + +Button is released => pass down to pointer-focussed element. + (actually: pass down to the element where the button-press was passed to) + Would eg. acivate the button, and restore the state of a pressed button. + + button_up + click + +Button remains pressed and pointer moves. + Means: We might be dragging something around. + Start a "drag" => pass down a "drag" event to pointer focussed element. + Keep track of drag start, and pass on relative drag motion down to element. + Keeps passing drag elements to same element until drag ends. + Would keep the element pointer focussed (?) + + A 'button' would ignore drags. drag_begin, drag_end, drag_motion ? + A 'titlebar' would use this to begin a move, and update position. + A 'iconified' would use this to de-couple from eg. dock + + drags have a pointer button associated (left, middle, right), + and a relative position since. They also have the starting position, + relative to the element. + + button_down + [lingering time, some light move] + drag_begin + drag_motion + drag_motion + button_up + drag_end + +Button is pressed again, without much move since last press. + Means: We have a double-click. + Pass down a double-click to the pointer-focussed element. + + button_down + button_up + double_click + + +## Dock and Clip class elements + +```plantuml +class Dock { + Container container[] + DockEntry entries[] +} + +class DockEntry { + Element +} + +class Launcher { +} +DockEntry <|-- Launcher + +class Icon {} +DockEntry <|-- Icon + +class IconSurface {} +DockEntry <|-- IconSurface + +class Clip {} +Dock <|-- Clip + +class IconArea {} +Dock <|-- IconArea +``` + diff --git a/src/toolkit/toolkit_test.c b/src/toolkit/toolkit_test.c index 89fbcef5..f34355f9 100644 --- a/src/toolkit/toolkit_test.c +++ b/src/toolkit/toolkit_test.c @@ -22,6 +22,23 @@ /** Toolkit unit tests. */ const bs_test_set_t toolkit_tests[] = { + { 1, "bordered", wlmtk_bordered_test_cases }, + { 1, "box", wlmtk_box_test_cases }, + { 1, "button", wlmtk_button_test_cases }, + { 1, "container", wlmtk_container_test_cases }, + { 1, "content", wlmtk_content_test_cases }, + { 1, "element", wlmtk_element_test_cases }, + { 1, "fsm", wlmtk_fsm_test_cases }, + { 1, "surface", wlmtk_surface_test_cases }, + { 1, "rectangle", wlmtk_rectangle_test_cases }, + { 1, "resizebar", wlmtk_resizebar_test_cases }, + { 1, "resizebar_area", wlmtk_resizebar_area_test_cases }, + { 1, "titlebar", wlmtk_titlebar_test_cases }, + { 1, "titlebar_button", wlmtk_titlebar_button_test_cases }, + { 1, "titlebar_title", wlmtk_titlebar_title_test_cases }, + { 1, "util", wlmtk_util_test_cases }, + { 1, "window", wlmtk_window_test_cases }, + { 1, "workspace", wlmtk_workspace_test_cases }, { 1, "primitives", wlmaker_primitives_test_cases }, { 0, NULL, NULL } }; diff --git a/src/toolkit/util.c b/src/toolkit/util.c new file mode 100644 index 00000000..92419400 --- /dev/null +++ b/src/toolkit/util.c @@ -0,0 +1,90 @@ +/* ========================================================================= */ +/** + * @file util.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "util.h" + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +void wlmtk_util_connect_listener_signal( + struct wl_signal *signal_ptr, + struct wl_listener *listener_ptr, + void (*notifier_func)(struct wl_listener *, void *)) +{ + listener_ptr->notify = notifier_func; + wl_signal_add(signal_ptr, listener_ptr); +} + +/* == Unit tests =========================================================== */ + +static void test_listener(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_util_test_cases[] = { + { 1, "listener", test_listener }, + { 0, NULL, NULL } +}; + +/** Struct for testing listener code. */ +typedef struct { + /** Listener. */ + struct wl_listener listener; + /** Data. */ + int data; +} _wlmtk_util_listener; + +static void _wlmtk_util_listener_handler( + struct wl_listener *listener_ptr, + void *data_ptr); + +/* ------------------------------------------------------------------------- */ +/** A test to verify listener handlers are called in order of subscription. */ +static void test_listener(bs_test_t *test_ptr) +{ + struct wl_signal signal; + _wlmtk_util_listener l1 = {}, l2 = {}; + int i = 0; + + wl_signal_init(&signal); + wlmtk_util_connect_listener_signal( + &signal, &l1.listener, _wlmtk_util_listener_handler); + + BS_TEST_VERIFY_EQ(test_ptr, 0, l1.data); + wl_signal_emit(&signal, &i); + BS_TEST_VERIFY_EQ(test_ptr, 1, l1.data); + + wlmtk_util_connect_listener_signal( + &signal, &l2.listener, _wlmtk_util_listener_handler); + wl_signal_emit(&signal, &i); + BS_TEST_VERIFY_EQ(test_ptr, 2, l1.data); + BS_TEST_VERIFY_EQ(test_ptr, 3, l2.data); +} + +/** Test handler for the listener. */ +void _wlmtk_util_listener_handler( + struct wl_listener *listener_ptr, + void *data_ptr) +{ + _wlmtk_util_listener *wlmtk_util_listener_ptr = BS_CONTAINER_OF( + listener_ptr, _wlmtk_util_listener, listener); + int *i_ptr = data_ptr; + wlmtk_util_listener_ptr->data = ++(*i_ptr); +} + +/* == End of util.c ======================================================== */ diff --git a/src/util.h b/src/toolkit/util.h similarity index 86% rename from src/util.h rename to src/toolkit/util.h index 796432fd..f150d263 100644 --- a/src/util.h +++ b/src/toolkit/util.h @@ -17,9 +17,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef __WLMAKER_UTIL_H__ -#define __WLMAKER_UTIL_H__ +#ifndef __WLMTK_UTIL_H__ +#define __WLMTK_UTIL_H__ +#include #include #ifdef __cplusplus @@ -38,14 +39,17 @@ extern "C" { * @param listener_ptr * @param notifier_func */ -void wlm_util_connect_listener_signal( +void wlmtk_util_connect_listener_signal( struct wl_signal *signal_ptr, struct wl_listener *listener_ptr, void (*notifier_func)(struct wl_listener *, void *)); +/** Unit test cases. */ +extern const bs_test_case_t wlmtk_util_test_cases[]; + #ifdef __cplusplus } // extern "C" #endif // __cplusplus -#endif /* __WLMAKER_UTIL_H__ */ +#endif /* __WLMTK_UTIL_H__ */ /* == End of util.h ======================================================== */ diff --git a/src/toolkit/window.c b/src/toolkit/window.c new file mode 100644 index 00000000..620013a0 --- /dev/null +++ b/src/toolkit/window.c @@ -0,0 +1,1472 @@ +/* ========================================================================= */ +/** + * @file window.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "window.h" + +#include "rectangle.h" +#include "workspace.h" + +#include "wlr/util/box.h" + +/* == Declarations ========================================================= */ + +/** Maximum number of pending state updates. */ +#define WLMTK_WINDOW_MAX_PENDING 64 + +/** Virtual method table for the window. */ +struct _wlmtk_window_vmt_t { + /** Destructor. */ + void (*destroy)(wlmtk_window_t *window_ptr); + /** Virtual method for @ref wlmtk_window_request_minimize. */ + void (*request_minimize)(wlmtk_window_t *window_ptr); + /** Virtual method for @ref wlmtk_window_request_move. */ + void (*request_move)(wlmtk_window_t *window_ptr); + /** Virtual method for @ref wlmtk_window_request_resize. */ + void (*request_resize)(wlmtk_window_t *window_ptr, + uint32_t edges); +}; + +/** Pending positional updates for @ref wlmtk_window_t::content_ptr. */ +typedef struct { + /** Node within @ref wlmtk_window_t::pending_updates. */ + bs_dllist_node_t dlnode; + /** Serial of the update. */ + uint32_t serial; + /** Pending X position of the surface. */ + int x; + /** Pending Y position of the surface. */ + int y; + /** Surface's width that is to be committed at serial. */ + int width; + /** Surface's hehight that is to be committed at serial. */ + int height; +} wlmtk_pending_update_t; + +/** State of the window. */ +struct _wlmtk_window_t { + /** Superclass: Bordered. */ + wlmtk_bordered_t super_bordered; + /** Original virtual method table of the window's element superclass. */ + wlmtk_element_vmt_t orig_super_element_vmt; + /** Original virtual method table of the window' container superclass. */ + wlmtk_container_vmt_t orig_super_container_vmt; + + /** Virtual method table. */ + wlmtk_window_vmt_t vmt; + + /** Box: In `super_bordered`, holds surface, title bar and resizebar. */ + wlmtk_box_t box; + + /** FIXME: Element. */ + wlmtk_element_t *element_ptr; + /** Points to the workspace, if mapped. */ + wlmtk_workspace_t *workspace_ptr; + + /** Content of the window. */ + wlmtk_content_t *content_ptr; + /** Titlebar. */ + wlmtk_titlebar_t *titlebar_ptr; + /** Resizebar. */ + wlmtk_resizebar_t *resizebar_ptr; + + /** Window title. Set through @ref wlmtk_window_set_title. */ + char *title_ptr; + + /** Pending updates. */ + bs_dllist_t pending_updates; + /** List of udpates currently available. */ + bs_dllist_t available_updates; + /** Pre-alloocated updates. */ + wlmtk_pending_update_t pre_allocated_updates[WLMTK_WINDOW_MAX_PENDING]; + + /** Organic size of the window, ie. when not maximized. */ + struct wlr_box organic_size; + /** Whether the window has been requested as maximized. */ + bool maximized; + /** Whether the window has been requested as fullscreen. */ + bool fullscreen; + /** + * Whether an "inorganic" sizing operation is in progress, and thus size + * changes should not be recorded in @ref wlmtk_window_t::organic_size. + * + * This is eg. between @ref wlmtk_window_request_fullscreen and + * @ref wlmtk_window_commit_fullscreen. + */ + bool inorganic_sizing; + + /** + * Stores whether the window is server-side decorated. + * + * If the window is NOT fullscreen, then this is equivalent to + * (titlebar_ptr != NULL && resizebar_ptr != NULL). For a fullscreen + * window, titlebar and resizebar would be NULL, but the flag stores + * whether decoration should be enabled on organic/maximized modes. + */ + bool server_side_decorated; + /** Stores whether the window is activated (keyboard focus). */ + bool activated; +}; + +/** State of a fake window: Includes the public record and the window. */ +typedef struct { + /** Window state. */ + wlmtk_window_t window; + /** Fake window - public state. */ + wlmtk_fake_window_t fake_window; + /** Fake content. */ + wlmtk_content_t content; +} wlmtk_fake_window_state_t; + +static bool _wlmtk_window_init( + wlmtk_window_t *window_ptr, + wlmtk_env_t *env_ptr, + wlmtk_element_t *element_ptr); +static void _wlmtk_window_fini(wlmtk_window_t *window_ptr); +static wlmtk_window_vmt_t _wlmtk_window_extend( + wlmtk_window_t *window_ptr, + const wlmtk_window_vmt_t *window_vmt_ptr); + +static bool _wlmtk_window_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr); +static void _wlmtk_window_container_update_layout( + wlmtk_container_t *container_ptr); + +static void _wlmtk_window_request_minimize(wlmtk_window_t *window_ptr); +static void _wlmtk_window_request_move(wlmtk_window_t *window_ptr); +static void _wlmtk_window_request_resize( + wlmtk_window_t *window_ptr, + uint32_t edges); + +static void _wlmtk_window_create_titlebar(wlmtk_window_t *window_ptr); +static void _wlmtk_window_create_resizebar(wlmtk_window_t *window_ptr); +static void _wlmtk_window_destroy_titlebar(wlmtk_window_t *window_ptr); +static void _wlmtk_window_destroy_resizebar(wlmtk_window_t *window_ptr); +static void _wlmtk_window_apply_decoration(wlmtk_window_t *window_ptr); +static void _wlmtk_window_request_position_and_size_decorated( + wlmtk_window_t *window_ptr, + int x, + int y, + int width, + int height, + bool include_titlebar, + bool include_resizebar); + +static wlmtk_pending_update_t *_wlmtk_window_prepare_update( + wlmtk_window_t *window_ptr); +static void _wlmtk_window_release_update( + wlmtk_window_t *window_ptr, + wlmtk_pending_update_t *update_ptr); + +/* == Data ================================================================= */ + +/** Virtual method table for the window's element superclass. */ +static const wlmtk_element_vmt_t window_element_vmt = { + .pointer_button = _wlmtk_window_element_pointer_button, +}; +/** Virtual method table for the window's container superclass. */ +static const wlmtk_container_vmt_t window_container_vmt = { + .update_layout = _wlmtk_window_container_update_layout, +}; +/** Virtual method table for the window itself. */ +static const wlmtk_window_vmt_t _wlmtk_window_vmt = { + .request_minimize = _wlmtk_window_request_minimize, + .request_move = _wlmtk_window_request_move, + .request_resize = _wlmtk_window_request_resize, +}; + +/** Style of the title bar. */ +// TODO(kaeser@gubbe.ch): Move to central config. */ +static const wlmtk_titlebar_style_t titlebar_style = { + .focussed_fill = { + .type = WLMTK_STYLE_COLOR_HGRADIENT, + .param = { .hgradient = { .from = 0xff505a5e,.to = 0xff202a2e }} + }, + .blurred_fill = { + .type = WLMTK_STYLE_COLOR_HGRADIENT, + .param = { .hgradient = { .from = 0xffc2c0c5,.to = 0xff828085 }} + }, + .focussed_text_color = 0xffffffff, + .blurred_text_color = 0xff000000, + .height = 22, + .bezel_width = 1, + .margin_style = { .width = 1, .color = 0xff000000 }, +}; + +/** Style of the resize bar. */ +// TODO(kaeser@gubbe.ch): Move to central config. */ +static const wlmtk_resizebar_style_t resizebar_style = { + .fill = { + .type = WLMTK_STYLE_COLOR_SOLID, + .param = { .solid = { .color = 0xffc2c0c5 }} + }, + .height = 7, + .corner_width = 29, + .bezel_width = 1, + .margin_style = { .width = 0, .color = 0xff000000 }, +}; + +/** Style of the margin between title, surface and resizebar. */ +static const wlmtk_margin_style_t margin_style = { + .width = 1, + .color = 0xff000000, +}; + +/** Style of the border around the window. */ +static const wlmtk_margin_style_t border_style = { + .width = 1, + .color = 0xff000000, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +wlmtk_window_t *wlmtk_window_create( + wlmtk_content_t *content_ptr, + wlmtk_env_t *env_ptr) +{ + wlmtk_window_t *window_ptr = logged_calloc(1, sizeof(wlmtk_window_t)); + if (NULL == window_ptr) return NULL; + + if (!_wlmtk_window_init( + window_ptr, + env_ptr, + wlmtk_content_element(content_ptr))) { + wlmtk_window_destroy(window_ptr); + return NULL; + } + window_ptr->content_ptr = content_ptr; + wlmtk_content_set_window(content_ptr, window_ptr); + + return window_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_destroy(wlmtk_window_t *window_ptr) +{ + _wlmtk_window_fini(window_ptr); + free(window_ptr); +} + +/* ------------------------------------------------------------------------- */ +wlmtk_element_t *wlmtk_window_element(wlmtk_window_t *window_ptr) +{ + return &window_ptr->super_bordered.super_container.super_element; +} + +/* ------------------------------------------------------------------------- */ +wlmtk_window_t *wlmtk_window_from_element(wlmtk_element_t *element_ptr) +{ + wlmtk_window_t *window_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_window_t, super_bordered.super_container.super_element); + BS_ASSERT(_wlmtk_window_container_update_layout == + window_ptr->super_bordered.super_container.vmt.update_layout); + return window_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_set_activated( + wlmtk_window_t *window_ptr, + bool activated) +{ + window_ptr->activated = activated; + wlmtk_content_set_activated(window_ptr->content_ptr, activated); + if (NULL != window_ptr->titlebar_ptr) { + wlmtk_titlebar_set_activated(window_ptr->titlebar_ptr, activated); + } +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_window_is_activated(wlmtk_window_t *window_ptr) +{ + return window_ptr->activated; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_set_server_side_decorated( + wlmtk_window_t *window_ptr, + bool decorated) +{ + // TODO(kaeser@gubbe.ch): Implement. + bs_log(BS_INFO, "Set server side decoration for window %p: %d", + window_ptr, decorated); + + if (window_ptr->server_side_decorated == decorated) return; + window_ptr->server_side_decorated = decorated; + + _wlmtk_window_apply_decoration(window_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_set_title( + wlmtk_window_t *window_ptr, + const char *title_ptr) +{ + char *new_title_ptr = NULL; + if (NULL != title_ptr) { + new_title_ptr = logged_strdup(title_ptr); + BS_ASSERT(NULL != new_title_ptr); + } else { + char buf[64]; + snprintf(buf, sizeof(buf), "Unnamed window %p", window_ptr); + new_title_ptr = logged_strdup(buf); + BS_ASSERT(NULL != new_title_ptr); + } + + if (NULL != window_ptr->title_ptr) { + if (0 == strcmp(window_ptr->title_ptr, new_title_ptr)) { + free(new_title_ptr); + return; + } + free(window_ptr->title_ptr); + } + window_ptr->title_ptr = new_title_ptr; + + if (NULL != window_ptr->titlebar_ptr) { + wlmtk_titlebar_set_title(window_ptr->titlebar_ptr, + window_ptr->title_ptr); + } +} + +/* ------------------------------------------------------------------------- */ +const char *wlmtk_window_get_title(wlmtk_window_t *window_ptr) +{ + BS_ASSERT(NULL != window_ptr->title_ptr); + return window_ptr->title_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_request_close(wlmtk_window_t *window_ptr) +{ + wlmtk_content_request_close(window_ptr->content_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_request_minimize(wlmtk_window_t *window_ptr) +{ + window_ptr->vmt.request_minimize(window_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_request_maximized( + wlmtk_window_t *window_ptr, + bool maximized) +{ + BS_ASSERT(NULL != wlmtk_window_get_workspace(window_ptr)); + if (window_ptr->maximized == maximized) return; + if (window_ptr->fullscreen) return; + + window_ptr->inorganic_sizing = maximized; + + struct wlr_box box; + if (maximized) { + box = wlmtk_workspace_get_maximize_extents( + wlmtk_window_get_workspace(window_ptr)); + } else { + box = window_ptr->organic_size; + } + + wlmtk_content_request_maximized(window_ptr->content_ptr, maximized); + + _wlmtk_window_request_position_and_size_decorated( + window_ptr, box.x, box.y, box.width, box.height, + window_ptr->server_side_decorated, + window_ptr->server_side_decorated); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_commit_maximized( + wlmtk_window_t *window_ptr, + bool maximized) +{ + // Guard clause: Nothing to do if already as committed. + if (window_ptr->maximized == maximized) return; + + window_ptr->maximized = maximized; +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_window_is_maximized(wlmtk_window_t *window_ptr) +{ + return window_ptr->maximized; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_request_fullscreen( + wlmtk_window_t *window_ptr, + bool fullscreen) +{ + struct wlr_box box; + uint32_t serial; + wlmtk_pending_update_t *pending_update_ptr; + + // Must be mapped.x + BS_ASSERT(NULL != wlmtk_window_get_workspace(window_ptr)); + + // Will not line up another pending update. + wlmtk_content_request_fullscreen(window_ptr->content_ptr, fullscreen); + + window_ptr->inorganic_sizing = fullscreen; + + if (fullscreen) { + box = wlmtk_workspace_get_fullscreen_extents( + wlmtk_window_get_workspace(window_ptr)); + serial = wlmtk_content_request_size( + window_ptr->content_ptr, box.width, box.height); + pending_update_ptr = _wlmtk_window_prepare_update(window_ptr); + pending_update_ptr->serial = serial; + pending_update_ptr->x = box.x; + pending_update_ptr->y = box.y; + pending_update_ptr->width = box.width; + pending_update_ptr->height = box.height; + + } else { + + box = window_ptr->organic_size; + _wlmtk_window_request_position_and_size_decorated( + window_ptr, box.x, box.y, box.width, box.height, + window_ptr->server_side_decorated, + window_ptr->server_side_decorated); + } + +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_commit_fullscreen( + wlmtk_window_t *window_ptr, + bool fullscreen) +{ + // Guard clause: Nothing to do if we're already there. + if (window_ptr->fullscreen == fullscreen) return; + + // TODO(kaeser@gubbe.ch): For whatever reason, the node isn't displayed + // when we zero out the border with, or hide the border elements. + // Figure out what causes that, then get rid of the border on fullscreen. + if (false) { + wlmtk_margin_style_t bstyle = border_style; + if (fullscreen) bstyle.width = 0; + wlmtk_bordered_set_style(&window_ptr->super_bordered, &bstyle); + } + + window_ptr->fullscreen = fullscreen; + _wlmtk_window_apply_decoration(window_ptr); + + wlmtk_workspace_window_to_fullscreen( + wlmtk_window_get_workspace(window_ptr), window_ptr, fullscreen); +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_window_is_fullscreen(wlmtk_window_t *window_ptr) +{ + return window_ptr->fullscreen; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_request_move(wlmtk_window_t *window_ptr) +{ + window_ptr->vmt.request_move(window_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_request_resize(wlmtk_window_t *window_ptr, + uint32_t edges) +{ + window_ptr->vmt.request_resize(window_ptr, edges); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_set_position(wlmtk_window_t *window_ptr, int x, int y) +{ + window_ptr->organic_size.x = x; + window_ptr->organic_size.y = y; + wlmtk_element_set_position(wlmtk_window_element(window_ptr), x, y); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_get_size( + wlmtk_window_t *window_ptr, + int *width_ptr, + int *height_ptr) +{ + // TODO(kaeser@gubbe.ch): Add decoration, if server-side-decorated. + wlmtk_content_get_size(window_ptr->content_ptr, width_ptr, height_ptr); + + if (NULL != window_ptr->titlebar_ptr) { + *height_ptr += titlebar_style.height + margin_style.width; + } + if (NULL != window_ptr->resizebar_ptr) { + *height_ptr += resizebar_style.height + margin_style.width; + } + *height_ptr += 2 * window_ptr->super_bordered.style.width; + + *width_ptr += 2 * window_ptr->super_bordered.style.width; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_request_size( + wlmtk_window_t *window_ptr, + int width, + int height) +{ + // TODO(kaeser@gubbe.ch): Adjust for decoration size, if server-side. + wlmtk_content_request_size(window_ptr->content_ptr, width, height); + + // TODO(kaeser@gubbe.ch): For client surface (eg. a wlr_surface), setting + // the size is an asynchronous operation and should be handled as such. + // Meaning: In example of resizing at the top-left corner, we'll want to + // request the surface to adjust size, but wait with adjusting the + // surface position until the size adjustment is applied. This implies we + // may need to combine the request_size and set_position methods for window. +} + +/* ------------------------------------------------------------------------- */ +struct wlr_box wlmtk_window_get_position_and_size( + wlmtk_window_t *window_ptr) +{ + struct wlr_box box; + + wlmtk_element_get_position( + wlmtk_window_element(window_ptr), &box.x, &box.y); + wlmtk_window_get_size(window_ptr, &box.width, &box.height); + return box; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_request_position_and_size( + wlmtk_window_t *window_ptr, + int x, + int y, + int width, + int height) +{ + _wlmtk_window_request_position_and_size_decorated( + window_ptr, x, y, width, height, + NULL != window_ptr->titlebar_ptr, + NULL != window_ptr->resizebar_ptr); + + window_ptr->organic_size.x = x; + window_ptr->organic_size.y = y; + window_ptr->organic_size.width = width; + window_ptr->organic_size.height = height; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_serial(wlmtk_window_t *window_ptr, uint32_t serial) +{ + bs_dllist_node_t *dlnode_ptr; + + if (!window_ptr->inorganic_sizing && + NULL == window_ptr->pending_updates.head_ptr) { + wlmtk_window_get_size(window_ptr, + &window_ptr->organic_size.width, + &window_ptr->organic_size.height); + return; + } + + while (NULL != (dlnode_ptr = window_ptr->pending_updates.head_ptr)) { + wlmtk_pending_update_t *pending_update_ptr = BS_CONTAINER_OF( + dlnode_ptr, wlmtk_pending_update_t, dlnode); + + int32_t delta = pending_update_ptr->serial - serial; + if (0 < delta) break; + + wlmtk_element_set_position( + wlmtk_window_element(window_ptr), + pending_update_ptr->x, + pending_update_ptr->y); + _wlmtk_window_release_update(window_ptr, pending_update_ptr); + } +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_window_set_workspace( + wlmtk_window_t *window_ptr, + wlmtk_workspace_t *workspace_ptr) +{ + window_ptr->workspace_ptr = workspace_ptr; +} + +/* ------------------------------------------------------------------------- */ +wlmtk_workspace_t *wlmtk_window_get_workspace(wlmtk_window_t *window_ptr) +{ + return window_ptr->workspace_ptr; +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** + * Initializes an (allocated) window. + * + * @param window_ptr + * @param env_ptr + * @param element_ptr + * + * @return true on success. + */ +bool _wlmtk_window_init( + wlmtk_window_t *window_ptr, + wlmtk_env_t *env_ptr, + wlmtk_element_t *element_ptr) +{ + BS_ASSERT(NULL != window_ptr); + memcpy(&window_ptr->vmt, &_wlmtk_window_vmt, sizeof(wlmtk_window_vmt_t)); + + for (size_t i = 0; i < WLMTK_WINDOW_MAX_PENDING; ++i) { + bs_dllist_push_back(&window_ptr->available_updates, + &window_ptr->pre_allocated_updates[i].dlnode); + } + + if (!wlmtk_box_init(&window_ptr->box, env_ptr, + WLMTK_BOX_VERTICAL, + &margin_style)) { + _wlmtk_window_fini(window_ptr); + return false; + } + wlmtk_element_set_visible( + &window_ptr->box.super_container.super_element, true); + + if (!wlmtk_bordered_init(&window_ptr->super_bordered, + env_ptr, + &window_ptr->box.super_container.super_element, + &border_style)) { + _wlmtk_window_fini(window_ptr); + return false; + } + + window_ptr->orig_super_element_vmt = wlmtk_element_extend( + &window_ptr->super_bordered.super_container.super_element, + &window_element_vmt); + window_ptr->orig_super_container_vmt = wlmtk_container_extend( + &window_ptr->super_bordered.super_container, &window_container_vmt); + window_ptr->element_ptr = element_ptr; + + wlmtk_window_set_title(window_ptr, NULL); + + wlmtk_box_add_element_front(&window_ptr->box, element_ptr); + wlmtk_element_set_visible(element_ptr, true); + return true; +} + +/* ------------------------------------------------------------------------- */ +/** + * Uninitializes the window. + * + * @param window_ptr + */ +void _wlmtk_window_fini(wlmtk_window_t *window_ptr) +{ + wlmtk_window_set_server_side_decorated(window_ptr, false); + + if (NULL != window_ptr->content_ptr) { + wlmtk_content_set_window(window_ptr->content_ptr, NULL); + } + + if (NULL != window_ptr->element_ptr) { + wlmtk_box_remove_element( + &window_ptr->box, window_ptr->element_ptr); + wlmtk_element_set_visible(window_ptr->element_ptr, false); + window_ptr->element_ptr = NULL; + } + + if (NULL != window_ptr->title_ptr) { + free(window_ptr->title_ptr); + window_ptr->title_ptr = NULL; + } + + wlmtk_bordered_fini(&window_ptr->super_bordered); + wlmtk_box_fini(&window_ptr->box); +} + +/* ------------------------------------------------------------------------- */ +/** + * Extends the window's virtual methods. + * + * @param window_ptr + * @param window_vmt_ptr + * + * @return The previous virtual method table. + */ +wlmtk_window_vmt_t _wlmtk_window_extend( + wlmtk_window_t *window_ptr, + const wlmtk_window_vmt_t *window_vmt_ptr) +{ + wlmtk_window_vmt_t orig_vmt = window_ptr->vmt; + + if (NULL != window_vmt_ptr->request_minimize) { + window_ptr->vmt.request_minimize = window_vmt_ptr->request_minimize; + } + if (NULL != window_vmt_ptr->request_move) { + window_ptr->vmt.request_move = window_vmt_ptr->request_move; + } + if (NULL != window_vmt_ptr->request_resize) { + window_ptr->vmt.request_resize = window_vmt_ptr->request_resize; + } + + return orig_vmt; +} + +/* ------------------------------------------------------------------------- */ +/** Activates window on button press, and calls the parent's implementation. */ +bool _wlmtk_window_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr) +{ + wlmtk_window_t *window_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_window_t, super_bordered.super_container.super_element); + + // We shouldn't receive buttons when not mapped. + wlmtk_workspace_t *workspace_ptr = wlmtk_window_get_workspace(window_ptr); + wlmtk_workspace_activate_window(workspace_ptr, window_ptr); + + if (!window_ptr->fullscreen) { + wlmtk_workspace_raise_window(workspace_ptr, window_ptr); + } + + return window_ptr->orig_super_element_vmt.pointer_button( + element_ptr, button_event_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Implementation of @ref wlmtk_container_vmt_t::update_layout. + * + * Invoked when the window's contained elements triggered a layout update, + * and will use this to trigger (potential) size updates to the window + * decorations. + * + * @param container_ptr + */ +void _wlmtk_window_container_update_layout(wlmtk_container_t *container_ptr) +{ + wlmtk_window_t *window_ptr = BS_CONTAINER_OF( + container_ptr, wlmtk_window_t, super_bordered.super_container); + + window_ptr->orig_super_container_vmt.update_layout(container_ptr); + + if (NULL != window_ptr->content_ptr) { + int width; + wlmtk_content_get_size(window_ptr->content_ptr, &width, NULL); + if (NULL != window_ptr->titlebar_ptr) { + wlmtk_titlebar_set_width(window_ptr->titlebar_ptr, width); + } + if (NULL != window_ptr->resizebar_ptr) { + wlmtk_resizebar_set_width(window_ptr->resizebar_ptr, width); + } + } +} + +/* ------------------------------------------------------------------------- */ +/** Default implementation of @ref wlmtk_window_request_minimize. */ +void _wlmtk_window_request_minimize(wlmtk_window_t *window_ptr) +{ + bs_log(BS_INFO, "Requesting window %p to minimize.", window_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Default implementation of @ref wlmtk_window_request_move. */ +void _wlmtk_window_request_move(wlmtk_window_t *window_ptr) +{ + BS_ASSERT(NULL != wlmtk_window_get_workspace(window_ptr)); + wlmtk_workspace_begin_window_move( + wlmtk_window_get_workspace(window_ptr), window_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Default implementation of @ref wlmtk_window_request_resize. */ +void _wlmtk_window_request_resize(wlmtk_window_t *window_ptr, uint32_t edges) +{ + BS_ASSERT(NULL != wlmtk_window_get_workspace(window_ptr)); + wlmtk_workspace_begin_window_resize( + wlmtk_window_get_workspace(window_ptr), window_ptr, edges); +} + +/* ------------------------------------------------------------------------- */ +/** Creates the titlebar. Expects server_side_decorated to be set. */ +void _wlmtk_window_create_titlebar(wlmtk_window_t *window_ptr) +{ + BS_ASSERT(window_ptr->server_side_decorated && !window_ptr->fullscreen); + + // Guard clause: Don't add decoration. + if (NULL != window_ptr->titlebar_ptr) return; + + // Create decoration. + window_ptr->titlebar_ptr = wlmtk_titlebar_create( + window_ptr->super_bordered.super_container.super_element.env_ptr, + window_ptr, &titlebar_style); + BS_ASSERT(NULL != window_ptr->titlebar_ptr); + wlmtk_titlebar_set_activated( + window_ptr->titlebar_ptr, window_ptr->activated); + wlmtk_element_set_visible( + wlmtk_titlebar_element(window_ptr->titlebar_ptr), true); + // Hm, if the content has a popup that extends over the titlebar area, + // it'll be partially obscured. That will look odd... Well, let's + // address that problem once there's a situation. + wlmtk_box_add_element_front( + &window_ptr->box, + wlmtk_titlebar_element(window_ptr->titlebar_ptr)); +} + +/* ------------------------------------------------------------------------- */ +/** Creates the resizebar. Expects server_side_decorated to be set. */ +void _wlmtk_window_create_resizebar(wlmtk_window_t *window_ptr) +{ + BS_ASSERT(window_ptr->server_side_decorated && !window_ptr->fullscreen); + + // Guard clause: Don't add decoration. + if (NULL != window_ptr->resizebar_ptr) return; + + window_ptr->resizebar_ptr = wlmtk_resizebar_create( + window_ptr->super_bordered.super_container.super_element.env_ptr, + window_ptr, &resizebar_style); + BS_ASSERT(NULL != window_ptr->resizebar_ptr); + wlmtk_element_set_visible( + wlmtk_resizebar_element(window_ptr->resizebar_ptr), true); + wlmtk_box_add_element_back( + &window_ptr->box, + wlmtk_resizebar_element(window_ptr->resizebar_ptr)); +} + +/* ------------------------------------------------------------------------- */ +/** Destroys the titlebar. */ +void _wlmtk_window_destroy_titlebar(wlmtk_window_t *window_ptr) +{ + BS_ASSERT(!window_ptr->server_side_decorated || window_ptr->fullscreen); + + if (NULL == window_ptr->titlebar_ptr) return; + + wlmtk_box_remove_element( + &window_ptr->box, + wlmtk_titlebar_element(window_ptr->titlebar_ptr)); + wlmtk_titlebar_destroy(window_ptr->titlebar_ptr); + window_ptr->titlebar_ptr = NULL; +} + +/* ------------------------------------------------------------------------- */ +/** Destroys the resizebar. */ +void _wlmtk_window_destroy_resizebar(wlmtk_window_t *window_ptr) +{ + BS_ASSERT(!window_ptr->server_side_decorated || window_ptr->fullscreen); + + if (NULL == window_ptr->resizebar_ptr) return; + + wlmtk_box_remove_element( + &window_ptr->box, + wlmtk_resizebar_element(window_ptr->resizebar_ptr)); + wlmtk_resizebar_destroy(window_ptr->resizebar_ptr); + window_ptr->resizebar_ptr = NULL; +} + +/* ------------------------------------------------------------------------- */ +/** Applies window decoration depending on current state. */ +void _wlmtk_window_apply_decoration(wlmtk_window_t *window_ptr) +{ + if (window_ptr->server_side_decorated && !window_ptr->fullscreen) { + _wlmtk_window_create_titlebar(window_ptr); + _wlmtk_window_create_resizebar(window_ptr); + } else { + _wlmtk_window_destroy_titlebar(window_ptr); + _wlmtk_window_destroy_resizebar(window_ptr); + } +} + +/* ------------------------------------------------------------------------- */ +/** + * Helper: Requests position and size, factoring in decoration. + * + * @param window_ptr + * @param x + * @param y + * @param width + * @param height + * @param include_titlebar + * @param include_resizebar + */ +void _wlmtk_window_request_position_and_size_decorated( + wlmtk_window_t *window_ptr, + int x, + int y, + int width, + int height, + bool include_titlebar, + bool include_resizebar) +{ + // Correct for borders, margin and decoration. + if (include_titlebar) { + height -= titlebar_style.height + margin_style.width; + } + if (include_resizebar) { + height -= resizebar_style.height + margin_style.width; + } + height -= 2 * border_style.width; + width -= 2 * border_style.width; + height = BS_MAX(0, height); + width = BS_MAX(0, width); + + uint32_t serial = wlmtk_content_request_size( + window_ptr->content_ptr, width, height); + + wlmtk_pending_update_t *pending_update_ptr = + _wlmtk_window_prepare_update(window_ptr); + pending_update_ptr->serial = serial; + pending_update_ptr->x = x; + pending_update_ptr->y = y; + pending_update_ptr->width = width; + pending_update_ptr->height = height; + + // TODO(kaeser@gubbe.ch): Handle synchronous case: @ref wlmtk_window_serial + // may have been called early, so we should check if serial had just been + // called before (or is below the last @wlmt_window_serial). In that case, + // the pending state should be applied right away. +} + +/* ------------------------------------------------------------------------- */ +/** + * Prepares a positional update: Allocates an item and attach it to the end + * of the list of pending updates. + * + * @param window_ptr + * + * @return A pointer to a @ref wlmtk_pending_update_t, already positioned at the + * back of @ref wlmtk_window_t::pending_updates. + */ +wlmtk_pending_update_t *_wlmtk_window_prepare_update( + wlmtk_window_t *window_ptr) +{ + bs_dllist_node_t *dlnode_ptr = bs_dllist_pop_front( + &window_ptr->available_updates); + if (NULL == dlnode_ptr) { + dlnode_ptr = bs_dllist_pop_front(&window_ptr->pending_updates); + bs_log(BS_WARNING, "Window %p: No updates available.", window_ptr); + // TODO(kaeser@gubbe.ch): Hm, should we apply this (old) update? + } + wlmtk_pending_update_t *update_ptr = BS_CONTAINER_OF( + dlnode_ptr, wlmtk_pending_update_t, dlnode); + bs_dllist_push_back(&window_ptr->pending_updates, &update_ptr->dlnode); + return update_ptr; +} + +/* ------------------------------------------------------------------------- */ +/** + * Releases a pending positional update. Moves it to the list of + * @ref wlmtk_window_t::available_updates. + * + * @param window_ptr + * @param update_ptr + */ +void _wlmtk_window_release_update( + wlmtk_window_t *window_ptr, + wlmtk_pending_update_t *update_ptr) +{ + bs_dllist_remove(&window_ptr->pending_updates, &update_ptr->dlnode); + bs_dllist_push_front(&window_ptr->available_updates, &update_ptr->dlnode); +} + +/* == Implementation of the fake window ==================================== */ + +static void _wlmtk_fake_window_request_minimize(wlmtk_window_t *window_ptr); +static void _wlmtk_fake_window_request_move(wlmtk_window_t *window_ptr); +static void _wlmtk_fake_window_request_resize( + wlmtk_window_t *window_ptr, + uint32_t edges); + +/** Virtual method table for the fake window itself. */ +static const wlmtk_window_vmt_t _wlmtk_fake_window_vmt = { + .request_minimize = _wlmtk_fake_window_request_minimize, + .request_move = _wlmtk_fake_window_request_move, + .request_resize = _wlmtk_fake_window_request_resize, + +}; + +/* ------------------------------------------------------------------------- */ +wlmtk_fake_window_t *wlmtk_fake_window_create(void) +{ + wlmtk_fake_window_state_t *fake_window_state_ptr = logged_calloc( + 1, sizeof(wlmtk_fake_window_state_t)); + if (NULL == fake_window_state_ptr) return NULL; + + fake_window_state_ptr->fake_window.fake_surface_ptr = + wlmtk_fake_surface_create(); + if (NULL == fake_window_state_ptr->fake_window.fake_surface_ptr) { + wlmtk_fake_window_destroy(&fake_window_state_ptr->fake_window); + return NULL; + } + + wlmtk_content_init( + &fake_window_state_ptr->content, + &fake_window_state_ptr->fake_window.fake_surface_ptr->surface, + NULL); + fake_window_state_ptr->fake_window.content_ptr = &fake_window_state_ptr->content; + + if (!_wlmtk_window_init( + &fake_window_state_ptr->window, + NULL, + wlmtk_content_element(&fake_window_state_ptr->content) + )) { + wlmtk_fake_window_destroy(&fake_window_state_ptr->fake_window); + return NULL; + } + fake_window_state_ptr->fake_window.window_ptr = + &fake_window_state_ptr->window; + fake_window_state_ptr->fake_window.window_ptr->content_ptr = + &fake_window_state_ptr->content; + + wlmtk_content_set_window( + &fake_window_state_ptr->content, + fake_window_state_ptr->fake_window.window_ptr); + + // Extend. We don't save the VMT, since it's for fake only. + _wlmtk_window_extend(&fake_window_state_ptr->window, + &_wlmtk_fake_window_vmt); + return &fake_window_state_ptr->fake_window; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_fake_window_destroy(wlmtk_fake_window_t *fake_window_ptr) +{ + wlmtk_fake_window_state_t *fake_window_state_ptr = BS_CONTAINER_OF( + fake_window_ptr, wlmtk_fake_window_state_t, fake_window); + + _wlmtk_window_fini(&fake_window_state_ptr->window); + + wlmtk_content_fini(&fake_window_state_ptr->content); + + if (NULL != fake_window_state_ptr->fake_window.fake_surface_ptr) { + wlmtk_fake_surface_destroy( + fake_window_state_ptr->fake_window.fake_surface_ptr); + fake_window_state_ptr->fake_window.fake_surface_ptr = NULL; + } + + free(fake_window_state_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Calls commit_size with the fake surface's serial and dimensions. */ +void wlmtk_fake_window_commit_size(wlmtk_fake_window_t *fake_window_ptr) +{ + wlmtk_content_commit_size( + fake_window_ptr->content_ptr, + fake_window_ptr->fake_surface_ptr->serial, + fake_window_ptr->fake_surface_ptr->requested_width, + fake_window_ptr->fake_surface_ptr->requested_height); +} + +/* ------------------------------------------------------------------------- */ +/** Fake implementation of @ref wlmtk_window_request_minimize. Records call. */ +void _wlmtk_fake_window_request_minimize(wlmtk_window_t *window_ptr) +{ + wlmtk_fake_window_state_t *fake_window_state_ptr = BS_CONTAINER_OF( + window_ptr, wlmtk_fake_window_state_t, window); + fake_window_state_ptr->fake_window.request_minimize_called = true; +} + +/* ------------------------------------------------------------------------- */ +/** Fake implementation of @ref wlmtk_window_request_move. Records call */ +void _wlmtk_fake_window_request_move(wlmtk_window_t *window_ptr) +{ + wlmtk_fake_window_state_t *fake_window_state_ptr = BS_CONTAINER_OF( + window_ptr, wlmtk_fake_window_state_t, window); + fake_window_state_ptr->fake_window.request_move_called = true; +} + +/* ------------------------------------------------------------------------- */ +/** Fake implementation of @ref wlmtk_window_request_resize. Records call. */ +void _wlmtk_fake_window_request_resize( + wlmtk_window_t *window_ptr, + uint32_t edges) +{ + wlmtk_fake_window_state_t *fake_window_state_ptr = BS_CONTAINER_OF( + window_ptr, wlmtk_fake_window_state_t, window); + fake_window_state_ptr->fake_window.request_resize_called = true; + fake_window_state_ptr->fake_window.request_resize_edges = edges; +} + +/* == Unit tests =========================================================== */ + +static void test_create_destroy(bs_test_t *test_ptr); +static void test_set_title(bs_test_t *test_ptr); +static void test_request_close(bs_test_t *test_ptr); +static void test_set_activated(bs_test_t *test_ptr); +static void test_server_side_decorated(bs_test_t *test_ptr); +static void test_maximize(bs_test_t *test_ptr); +static void test_fullscreen(bs_test_t *test_ptr); +static void test_fullscreen_unmap(bs_test_t *test_ptr); +static void test_fake(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_window_test_cases[] = { + { 1, "create_destroy", test_create_destroy }, + { 1, "set_title", test_set_title }, + { 1, "request_close", test_request_close }, + { 1, "set_activated", test_set_activated }, + { 1, "set_server_side_decorated", test_server_side_decorated }, + { 1, "maximize", test_maximize }, + { 1, "fullscreen", test_fullscreen }, + { 1, "fullscreen_unmap", test_fullscreen_unmap }, + { 1, "fake", test_fake }, + { 0, NULL, NULL } +}; + +/* ------------------------------------------------------------------------- */ +/** Tests setup and teardown. */ +void test_create_destroy(bs_test_t *test_ptr) +{ + wlmtk_fake_surface_t *fake_surface_ptr = wlmtk_fake_surface_create(); + wlmtk_content_t content; + wlmtk_content_init(&content, &fake_surface_ptr->surface, NULL); + wlmtk_window_t *window_ptr = wlmtk_window_create(&content, NULL); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, window_ptr, content.window_ptr); + + wlmtk_window_destroy(window_ptr); + wlmtk_content_fini(&content); + wlmtk_fake_surface_destroy(fake_surface_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests title. */ +void test_set_title(bs_test_t *test_ptr) +{ + wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); + BS_ASSERT(NULL != fw_ptr); + + wlmtk_window_set_title(fw_ptr->window_ptr, "Title"); + BS_TEST_VERIFY_STREQ( + test_ptr, + "Title", + wlmtk_window_get_title(fw_ptr->window_ptr)); + + wlmtk_window_set_title(fw_ptr->window_ptr, NULL); + BS_TEST_VERIFY_STRMATCH( + test_ptr, + wlmtk_window_get_title(fw_ptr->window_ptr), + "Unnamed window .*"); + + wlmtk_fake_window_destroy(fw_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests activation. */ +void test_request_close(bs_test_t *test_ptr) +{ + wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); + BS_ASSERT(NULL != fw_ptr); + + wlmtk_window_request_close(fw_ptr->window_ptr); + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->fake_surface_ptr->request_close_called); + + wlmtk_fake_window_destroy(fw_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests activation. */ +void test_set_activated(bs_test_t *test_ptr) +{ + wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); + BS_ASSERT(NULL != fw_ptr); + + wlmtk_window_set_activated(fw_ptr->window_ptr, true); + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->fake_surface_ptr->activated); + + wlmtk_window_set_activated(fw_ptr->window_ptr, false); + BS_TEST_VERIFY_FALSE(test_ptr, fw_ptr->fake_surface_ptr->activated); + + wlmtk_fake_window_destroy(fw_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests enabling and disabling server-side decoration. */ +void test_server_side_decorated(bs_test_t *test_ptr) +{ + wlmtk_fake_workspace_t *fws_ptr = wlmtk_fake_workspace_create(1024, 768); + BS_ASSERT(NULL != fws_ptr); + wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); + BS_ASSERT(NULL != fw_ptr); + + wlmtk_workspace_map_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + + BS_TEST_VERIFY_EQ(test_ptr, NULL, fw_ptr->window_ptr->titlebar_ptr); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fw_ptr->window_ptr->resizebar_ptr); + + wlmtk_window_set_server_side_decorated(fw_ptr->window_ptr, true); + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->window_ptr->server_side_decorated); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fw_ptr->window_ptr->titlebar_ptr); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fw_ptr->window_ptr->resizebar_ptr); + + // Maximize the window: We keep the decoration. + wlmtk_window_request_maximized(fw_ptr->window_ptr, true); + wlmtk_fake_window_commit_size(fw_ptr); + wlmtk_window_commit_maximized(fw_ptr->window_ptr, true); + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->window_ptr->server_side_decorated); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fw_ptr->window_ptr->titlebar_ptr); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fw_ptr->window_ptr->resizebar_ptr); + + // Make the window fullscreen: Hide the decoration. + wlmtk_window_request_fullscreen(fw_ptr->window_ptr, true); + wlmtk_fake_window_commit_size(fw_ptr); + wlmtk_window_commit_maximized(fw_ptr->window_ptr, false); + wlmtk_window_commit_fullscreen(fw_ptr->window_ptr, true); + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->window_ptr->server_side_decorated); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fw_ptr->window_ptr->titlebar_ptr); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fw_ptr->window_ptr->resizebar_ptr); + + // Back to organic size: Decoration is on. + wlmtk_window_request_fullscreen(fw_ptr->window_ptr, false); + wlmtk_fake_window_commit_size(fw_ptr); + wlmtk_window_commit_fullscreen(fw_ptr->window_ptr, false); + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->window_ptr->server_side_decorated); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fw_ptr->window_ptr->titlebar_ptr); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fw_ptr->window_ptr->resizebar_ptr); + + // Disable decoration. + wlmtk_window_set_server_side_decorated(fw_ptr->window_ptr, false); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fw_ptr->window_ptr->titlebar_ptr); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fw_ptr->window_ptr->resizebar_ptr); + + wlmtk_workspace_unmap_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + + wlmtk_fake_window_destroy(fw_ptr); + wlmtk_fake_workspace_destroy(fws_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests maximizing and un-maximizing a window. */ +void test_maximize(bs_test_t *test_ptr) +{ + struct wlr_box box; + + wlmtk_fake_workspace_t *fws_ptr = wlmtk_fake_workspace_create(1024, 768); + BS_ASSERT(NULL != fws_ptr); + wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); + BS_ASSERT(NULL != fw_ptr); + + // Window must be mapped to get maximized: Need workspace dimensions. + wlmtk_workspace_map_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + + // Set up initial organic size, and verify. + wlmtk_window_request_position_and_size(fw_ptr->window_ptr, 20, 10, 200, 100); + wlmtk_fake_window_commit_size(fw_ptr); + box = wlmtk_window_get_position_and_size(fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 20, box.x); + BS_TEST_VERIFY_EQ(test_ptr, 10, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 200, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 100, box.height); + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_maximized(fw_ptr->window_ptr)); + + // Re-position the window. + wlmtk_window_set_position(fw_ptr->window_ptr, 50, 30); + box = wlmtk_window_get_position_and_size(fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 50, box.x); + BS_TEST_VERIFY_EQ(test_ptr, 30, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 200, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 100, box.height); + + // Trigger another serial update. Should not change position nor size. + wlmtk_window_serial(fw_ptr->window_ptr, 1234); + box = wlmtk_window_get_position_and_size(fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 50, box.x); + BS_TEST_VERIFY_EQ(test_ptr, 30, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 200, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 100, box.height); + + // Maximize. + wlmtk_window_request_maximized(fw_ptr->window_ptr, true); + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_maximized(fw_ptr->window_ptr)); + wlmtk_fake_window_commit_size(fw_ptr); + wlmtk_window_commit_maximized(fw_ptr->window_ptr, true); + box = wlmtk_window_get_position_and_size(fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 0, box.x); + BS_TEST_VERIFY_EQ(test_ptr, 0, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 960, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 704, box.height); + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_maximized(fw_ptr->window_ptr)); + + // A second commit: should not overwrite the organic dimension. + wlmtk_fake_window_commit_size(fw_ptr); + + // Unmaximize. Restore earlier organic size and position. + wlmtk_window_request_maximized(fw_ptr->window_ptr, false); + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_maximized(fw_ptr->window_ptr)); + wlmtk_fake_window_commit_size(fw_ptr); + wlmtk_window_commit_maximized(fw_ptr->window_ptr, false); + box = wlmtk_window_get_position_and_size(fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 50, box.x); + BS_TEST_VERIFY_EQ(test_ptr, 30, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 200, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 100, box.height); + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_maximized(fw_ptr->window_ptr)); + + // TODO(kaeser@gubbe.ch): Define what should happen when a maximized + // window is moved. Should it lose maximization? Should it not move? + // Or just move on? + // Window Maker keeps maximization, but it's ... odd. + + wlmtk_workspace_unmap_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + wlmtk_fake_window_destroy(fw_ptr); + wlmtk_fake_workspace_destroy(fws_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests turning a window to fullscreen and back. */ +void test_fullscreen(bs_test_t *test_ptr) +{ + struct wlr_box box; + + wlmtk_fake_workspace_t *fws_ptr = wlmtk_fake_workspace_create(1024, 768); + BS_ASSERT(NULL != fws_ptr); + wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); + BS_ASSERT(NULL != fw_ptr); + + wlmtk_window_set_server_side_decorated(fw_ptr->window_ptr, true); + wlmtk_workspace_map_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->fake_surface_ptr->activated); + BS_TEST_VERIFY_EQ( + test_ptr, + fw_ptr->window_ptr, + wlmtk_workspace_get_activated_window(fws_ptr->workspace_ptr)); + + // Set up initial organic size, and verify. + wlmtk_window_request_position_and_size(fw_ptr->window_ptr, 20, 10, 200, 100); + wlmtk_fake_window_commit_size(fw_ptr); + box = wlmtk_window_get_position_and_size(fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 20, box.x); + BS_TEST_VERIFY_EQ(test_ptr, 10, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 200, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 100, box.height); + BS_TEST_VERIFY_FALSE(test_ptr, fw_ptr->window_ptr->inorganic_sizing); + + // Request fullscreen. Does not take immediate effect. + wlmtk_window_request_fullscreen(fw_ptr->window_ptr, true); + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_fullscreen(fw_ptr->window_ptr)); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_titlebar_is_activated(fw_ptr->window_ptr->titlebar_ptr)); + + // Only after "commit", it will take effect. + wlmtk_fake_window_commit_size(fw_ptr); + wlmtk_window_commit_fullscreen(fw_ptr->window_ptr, true); + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_fullscreen(fw_ptr->window_ptr)); + box = wlmtk_window_get_position_and_size(fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 0, box.x); + BS_TEST_VERIFY_EQ(test_ptr, 0, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 1024 + 2, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 768 + 2, box.height); + + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->fake_surface_ptr->activated); + BS_TEST_VERIFY_EQ( + test_ptr, + fw_ptr->window_ptr, + wlmtk_workspace_get_activated_window(fws_ptr->workspace_ptr)); + + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->window_ptr->server_side_decorated); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fw_ptr->window_ptr->titlebar_ptr); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fw_ptr->window_ptr->resizebar_ptr); + + // Request to end fullscreen. Not taking immediate effect. + wlmtk_window_request_fullscreen(fw_ptr->window_ptr, false); + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_fullscreen(fw_ptr->window_ptr)); + + // Takes effect after commit. We'll want the same position as before. + wlmtk_fake_window_commit_size(fw_ptr); + wlmtk_window_commit_fullscreen(fw_ptr->window_ptr, false); + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_fullscreen(fw_ptr->window_ptr)); + box = wlmtk_window_get_position_and_size(fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 20, box.x); + BS_TEST_VERIFY_EQ(test_ptr, 10, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 200, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 100, box.height); + + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->fake_surface_ptr->activated); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_titlebar_is_activated(fw_ptr->window_ptr->titlebar_ptr)); + BS_TEST_VERIFY_EQ( + test_ptr, + fw_ptr->window_ptr, + wlmtk_workspace_get_activated_window(fws_ptr->workspace_ptr)); + + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->window_ptr->server_side_decorated); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fw_ptr->window_ptr->titlebar_ptr); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fw_ptr->window_ptr->resizebar_ptr); + + wlmtk_workspace_unmap_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + wlmtk_fake_window_destroy(fw_ptr); + + wlmtk_fake_workspace_destroy(fws_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests that unmapping a fullscreen window works. */ +void test_fullscreen_unmap(bs_test_t *test_ptr) +{ + struct wlr_box box; + + wlmtk_fake_workspace_t *fws_ptr = wlmtk_fake_workspace_create(1024, 768); + BS_ASSERT(NULL != fws_ptr); + + wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); + BS_ASSERT(NULL != fw_ptr); + wlmtk_workspace_map_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->fake_surface_ptr->activated); + BS_TEST_VERIFY_EQ( + test_ptr, + fw_ptr->window_ptr, + wlmtk_workspace_get_activated_window(fws_ptr->workspace_ptr)); + + // Request fullscreen. Does not take immediate effect. + wlmtk_window_request_fullscreen(fw_ptr->window_ptr, true); + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_fullscreen(fw_ptr->window_ptr)); + + // Only after "commit", it will take effect. + wlmtk_fake_window_commit_size(fw_ptr); + wlmtk_window_commit_fullscreen(fw_ptr->window_ptr, true); + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_fullscreen(fw_ptr->window_ptr)); + box = wlmtk_window_get_position_and_size(fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 0, box.x); + BS_TEST_VERIFY_EQ(test_ptr, 0, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 1024 + 2, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 768 + 2, box.height); + BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->fake_surface_ptr->activated); + + wlmtk_workspace_unmap_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + BS_TEST_VERIFY_FALSE(test_ptr, fw_ptr->fake_surface_ptr->activated); + BS_TEST_VERIFY_EQ( + test_ptr, + NULL, + wlmtk_workspace_get_activated_window(fws_ptr->workspace_ptr)); + + wlmtk_fake_window_destroy(fw_ptr); + + wlmtk_fake_workspace_destroy(fws_ptr); +} + +// FIXME: Test that fullscreen keeps window decoration as it should. + +/* ------------------------------------------------------------------------- */ +/** Tests fake window ctor and dtor. */ +void test_fake(bs_test_t *test_ptr) +{ + wlmtk_fake_window_t *fake_window_ptr = wlmtk_fake_window_create(); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, fake_window_ptr); + wlmtk_fake_window_destroy(fake_window_ptr); +} + +/* == End of window.c ====================================================== */ diff --git a/src/toolkit/window.h b/src/toolkit/window.h new file mode 100644 index 00000000..52d8e565 --- /dev/null +++ b/src/toolkit/window.h @@ -0,0 +1,396 @@ +/* ========================================================================= */ +/** + * @file window.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_WINDOW_H__ +#define __WLMTK_WINDOW_H__ + +/** Forward declaration: Window. */ +typedef struct _wlmtk_window_t wlmtk_window_t; +/** Forward declaration: Virtual method table. */ +typedef struct _wlmtk_window_vmt_t wlmtk_window_vmt_t; + +#include "bordered.h" +#include "box.h" +#include "content.h" +#include "element.h" +#include "resizebar.h" +#include "surface.h" +#include "titlebar.h" +#include "workspace.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Creates a window for the given content. + * + * @param env_ptr + * @param content_ptr + * + * @return Pointer to the window state, or NULL on error. Must be free'd + * by calling @ref wlmtk_window_destroy. + */ +wlmtk_window_t *wlmtk_window_create( + wlmtk_content_t *content_ptr, + wlmtk_env_t *env_ptr); + +/** + * Destroys the window. + * + * @param window_ptr + */ +void wlmtk_window_destroy(wlmtk_window_t *window_ptr); + +/** + * Returns the super Element of the window. + * + * TODO(kaeser@gubbe.ch): Re-evaluate whether to work with accessors, or + * whether to keep the ancestry members public. + * + * @param window_ptr + * + * @return Pointer to the @ref wlmtk_element_t base instantiation to + * window_ptr. + */ +wlmtk_element_t *wlmtk_window_element(wlmtk_window_t *window_ptr); + +/** + * Returns the window from the super Element. + * + * @param element_ptr + * + * @return Pointer to the @ref wlmtk_window_t, for which `element_ptr` is + * the ancestor. + */ +wlmtk_window_t *wlmtk_window_from_element(wlmtk_element_t *element_ptr); + +/** + * Sets the window as activated, depending on the argument's value. + * + * An activated window will have keyboard focus and would have distinct + * decorations to indicate state. + * + * @param window_ptr + * @param activated + */ +void wlmtk_window_set_activated( + wlmtk_window_t *window_ptr, + bool activated); + +/** + * Returns whether the window is activated (has keyboard focus). + * + * @param window_ptr + * + * @return activation status. + */ +bool wlmtk_window_is_activated(wlmtk_window_t *window_ptr); + +/** + * Sets whether to have server-side decorations for this window. + * + * @param window_ptr + * @param decorated + */ +void wlmtk_window_set_server_side_decorated( + wlmtk_window_t *window_ptr, + bool decorated); + +/** + * Sets the title for the window. + * + * If `title_ptr` is NULL, a generic name is set. + * + * @param window_ptr + * @param title_ptr May be NULL. + */ +void wlmtk_window_set_title( + wlmtk_window_t *window_ptr, + const char *title_ptr); + +/** + * Returns the title of the window. + * + * @param window_ptr + * + * @returns Pointer to the window title. Will remain valid until the next call + * to @ref wlmtk_window_set_title, or until the window is destroyed. Will + * never be NULL. + */ +const char *wlmtk_window_get_title(wlmtk_window_t *window_ptr); + +/** + * Requests to close the window. + * + * @param window_ptr + */ +void wlmtk_window_request_close(wlmtk_window_t *window_ptr); + +/** + * Requests to minimize (iconify) the window. + * + * @param window_ptr + */ +void wlmtk_window_request_minimize(wlmtk_window_t *window_ptr); + +/** + * Requests a move for the window. + * + * Requires the window to be mapped (to a workspace), and forwards the call to + * @ref wlmtk_workspace_begin_window_move. + * + * @param window_ptr + */ +void wlmtk_window_request_move(wlmtk_window_t *window_ptr); + +/** + * Requests the window to be resized. + * + * Requires the window to be mapped (to a workspace), and forwards the call to + * @ref wlmtk_workspace_begin_window_resize. + * + * @param window_ptr + * @param edges + */ +void wlmtk_window_request_resize(wlmtk_window_t *window_ptr, + uint32_t edges); + +/** + * Sets the window's position. This is a synchronous operation. + * + * Updates the position in @ref wlmtk_window_t::organic_size. + * @param window_ptr + * @param x + * @param y + */ +void wlmtk_window_set_position(wlmtk_window_t *window_ptr, int x, int y); + +/** + * Obtains the size of the window, including potential decorations. + * + * @param window_ptr + * @param width_ptr May be NULL. + * @param height_ptr May be NULL. + */ +void wlmtk_window_get_size( + wlmtk_window_t *window_ptr, + int *width_ptr, + int *height_ptr); + +/** + * Requests a new size for the window, including potential decorations. + * + * This may be implemented as an asynchronous operation. + * + * @param window_ptr + * @param width + * @param height + */ +void wlmtk_window_request_size( + wlmtk_window_t *window_ptr, + int width, + int height); + +/** + * Reuests the window to be maximized. + * + * Requires the window to be mapped (to a workspace). Will lookup the maximize + * extents from the workspace, and request a corresponding updated position and + * size for the window. @ref wlmtk_window_t::organic_size will not be updated. + * + * This may be implemented as an asynchronous operation. Maximization will be + * applied once the size change has been committed by the surface. + * + * @param window_ptr + * @param maximized + */ +void wlmtk_window_request_maximized( + wlmtk_window_t *window_ptr, + bool maximized); + +/** + * Commits the `maximized` mode for the window. + * + * This is the "commit" part of the potentially asynchronous operation. To be + * called by @ref wlmtk_content_t, after @ref wlmtk_content_request_maximized + * has completed by the client. + * + * The call is idempotent: Once the window is committed, further calls with + * the same `maximized` value will return straight away. + * + * @param window_ptr + * @param maximized + */ +void wlmtk_window_commit_maximized( + wlmtk_window_t *window_ptr, + bool maximized); + +/** Returns whether the window is currently (requested to be) maximized. */ +bool wlmtk_window_is_maximized(wlmtk_window_t *window_ptr); + +/** + * Requests the window to be made fullscreen (or stops so). + * + * Requires the window to be mapped (to a workspace). This may be implemented + * as an asynchronous operation. Once the window content is ready, it should + * call @ref wlmtk_window_commit_fullscreen to complete the operation. + * + * @param window_ptr + * @param fullscreen Whether to enable fullscreen mode. + */ +void wlmtk_window_request_fullscreen( + wlmtk_window_t *window_ptr, + bool fullscreen); + +/** + * Commits the fullscreen mode for the window. + * + * This is the "commit" part of the potentially asynchronous operation. To be + * called by @ref wlmtk_content_t, after @ref wlmtk_content_request_fullscreen + * has completed by the client. + * + * The call is idempotent: Once the window is committed, further calls with + * the same `fullscreen` value will return straight away. + * + * @param window_ptr + * @param fullscreen + */ +void wlmtk_window_commit_fullscreen( + wlmtk_window_t *window_ptr, + bool fullscreen); + +/** + * Returns whether the window is currently in fullscreen mode. + * + * Will return the state after @ref wlmtk_window_commit_fullscreen has + * completed. + * + * @param window_ptr + * + * @return Whether it's in fullscreen mode or not. + */ +bool wlmtk_window_is_fullscreen(wlmtk_window_t *window_ptr); + + +/** + * Returns the current position and size of the window. + * + * @param window_ptr + * + * @return The position of the window (the window's element), and the currently + * committed width and height of the window. + */ +struct wlr_box wlmtk_window_get_position_and_size( + wlmtk_window_t *window_ptr); + +/** + * Requests an updated position and size for the window, including potential + * decorations. + * + * This may be implemented as an asynchronous operation. The re-positioning + * will be applied only once the size change has been committed by the client. + * + * The position and size will be stored in @ref wlmtk_window_t::organic_size. + * + * @param window_ptr + * @param x + * @param y + * @param width + * @param height + */ +void wlmtk_window_request_position_and_size( + wlmtk_window_t *window_ptr, + int x, + int y, + int width, + int height); + +/** + * Updates the window state to what was requested at the `serial`. + * + * Used for example when resizing a window from the top or left edges. In that + * case, @ref wlmtk_surface_request_size may be asynchronous and returns a + * serial. The surface is expected to call @ref wlmtk_window_serial with the + * returned serial when the size is committed. + * Only then, the corresponding positional update on the top/left edges are + * supposed to be applied. + * + * @ref wlmtk_window_t::organic_size will be updated, if there was no pending + * update: Meaning that the commit originated not from an earlier + * @ref wlmtk_window_request_position_and_size or @ref + * wlmtk_window_request_maximized call. + * + * @param window_ptr + * @param serial + */ +void wlmtk_window_serial(wlmtk_window_t *window_ptr, uint32_t serial); + +/** + * Sets @ref wlmtk_window_t::workspace_ptr. + * + * Protected method, to be called only from @ref wlmtk_workspace_t. + * + * @param window_ptr + * @param workspace_ptr + */ +void wlmtk_window_set_workspace( + wlmtk_window_t *window_ptr, + wlmtk_workspace_t *workspace_ptr); + +/** @return The value of @ref wlmtk_window_t::workspace_ptr. */ +wlmtk_workspace_t *wlmtk_window_get_workspace(wlmtk_window_t *window_ptr); + +/* ------------------------------------------------------------------------- */ + +/** State of the fake window, for tests. */ +typedef struct { + /** Window state. */ + wlmtk_window_t *window_ptr; + /** Fake surface, to manipulate the fake window's surface. */ + wlmtk_fake_surface_t *fake_surface_ptr; + /** Content, wraps the fake surface. */ + wlmtk_content_t *content_ptr; + + /** Whether @ref wlmtk_window_request_minimize was called. */ + bool request_minimize_called; + /** Whether @ref wlmtk_window_request_move was called. */ + bool request_move_called; + /** Whether @ref wlmtk_window_request_resize was called. */ + bool request_resize_called; + /** Argument to last @ref wlmtk_window_request_resize call. */ + uint32_t request_resize_edges; +} wlmtk_fake_window_t; + +/** Ctor. */ +wlmtk_fake_window_t *wlmtk_fake_window_create(void); +/** Dtor. */ +void wlmtk_fake_window_destroy(wlmtk_fake_window_t *fake_window_ptr); +/** Calls commit_size with the fake surface's serial and dimensions. */ +void wlmtk_fake_window_commit_size(wlmtk_fake_window_t *fake_window_ptr); + +/** Unit tests for window. */ +extern const bs_test_case_t wlmtk_window_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_WINDOW_H__ */ +/* == End of window.h ====================================================== */ diff --git a/src/toolkit/workspace.c b/src/toolkit/workspace.c new file mode 100644 index 00000000..0314a634 --- /dev/null +++ b/src/toolkit/workspace.c @@ -0,0 +1,1091 @@ +/* ========================================================================= */ +/** + * @file workspace.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "workspace.h" + +#include "fsm.h" + +#define WLR_USE_UNSTABLE +#include +#include +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +/** State of the workspace. */ +struct _wlmtk_workspace_t { + /** Superclass: Container. */ + wlmtk_container_t super_container; + /** Original virtual method table. We're overwriting parts. */ + wlmtk_element_vmt_t orig_super_element_vmt; + + /** Current FSM state. */ + wlmtk_fsm_t fsm; + + /** Container that holds the windows, ie. the window layer. */ + wlmtk_container_t window_container; + /** Container that holds the fullscreen elements. Should have only one. */ + wlmtk_container_t fullscreen_container; + + /** The activated window. */ + wlmtk_window_t *activated_window_ptr; + + /** The grabbed window. */ + wlmtk_window_t *grabbed_window_ptr; + /** Motion X */ + int motion_x; + /** Motion Y */ + int motion_y; + /** Element's X position when initiating a move or resize. */ + int initial_x; + /** Element's Y position when initiating a move or resize. */ + int initial_y; + /** Window's width when initiazing the resize. */ + int initial_width; + /** Window's height when initiazing the resize. */ + int initial_height; + /** Edges currently active for resizing: `enum wlr_edges`. */ + uint32_t resize_edges; + + /** Top left X coordinate of workspace. */ + int x1; + /** Top left Y coordinate of workspace. */ + int y1; + /** Bottom right X coordinate of workspace. */ + int x2; + /** Bottom right Y coordinate of workspace. */ + int y2; +}; + +static void _wlmtk_workspace_element_destroy(wlmtk_element_t *element_ptr); +static void _wlmtk_workspace_element_get_dimensions( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr); +static void _wlmtk_workspace_element_get_pointer_area( + wlmtk_element_t *element_ptr, + int *x1_ptr, + int *y1_ptr, + int *x2_ptr, + int *y2_ptr); +static bool _wlmtk_workspace_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, double y, + uint32_t time_msec); +static bool _wlmtk_workspace_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr); +static void _wlmtk_workspace_element_pointer_leave( + wlmtk_element_t *element_ptr); + +static bool pfsm_move_begin(wlmtk_fsm_t *fsm_ptr, void *ud_ptr); +static bool pfsm_move_motion(wlmtk_fsm_t *fsm_ptr, void *ud_ptr); +static bool pfsm_resize_begin(wlmtk_fsm_t *fsm_ptr, void *ud_ptr); +static bool pfsm_resize_motion(wlmtk_fsm_t *fsm_ptr, void *ud_ptr); +static bool pfsm_reset(wlmtk_fsm_t *fsm_ptr, void *ud_ptr); + +/* == Data ================================================================= */ + +/** States of the pointer FSM. */ +typedef enum { + PFSMS_PASSTHROUGH, + PFSMS_MOVE, + PFSMS_RESIZE +} pointer_state_t; + +/** Events for the pointer FSM. */ +typedef enum { + PFSME_BEGIN_MOVE, + PFSME_BEGIN_RESIZE, + PFSME_RELEASED, + PFSME_MOTION, + PFSME_RESET, +} pointer_state_event_t; + +/** Extensions to the workspace's super element's virtual methods. */ +const wlmtk_element_vmt_t workspace_element_vmt = { + .destroy = _wlmtk_workspace_element_destroy, + .get_dimensions = _wlmtk_workspace_element_get_dimensions, + .get_pointer_area = _wlmtk_workspace_element_get_pointer_area, + .pointer_motion = _wlmtk_workspace_element_pointer_motion, + .pointer_button = _wlmtk_workspace_element_pointer_button, + .pointer_leave = _wlmtk_workspace_element_pointer_leave, +}; + +/** Finite state machine definition for pointer events. */ +static const wlmtk_fsm_transition_t pfsm_transitions[] = { + { PFSMS_PASSTHROUGH, PFSME_BEGIN_MOVE, PFSMS_MOVE, pfsm_move_begin }, + { PFSMS_MOVE, PFSME_MOTION, PFSMS_MOVE, pfsm_move_motion }, + { PFSMS_MOVE, PFSME_RELEASED, PFSMS_PASSTHROUGH, pfsm_reset }, + { PFSMS_MOVE, PFSME_RESET, PFSMS_PASSTHROUGH, pfsm_reset }, + { PFSMS_PASSTHROUGH, PFSME_BEGIN_RESIZE, PFSMS_RESIZE, pfsm_resize_begin }, + { PFSMS_RESIZE, PFSME_MOTION, PFSMS_RESIZE, pfsm_resize_motion }, + { PFSMS_RESIZE, PFSME_RELEASED, PFSMS_PASSTHROUGH, pfsm_reset }, + { PFSMS_RESIZE, PFSME_RESET, PFSMS_PASSTHROUGH, pfsm_reset }, + WLMTK_FSM_TRANSITION_SENTINEL, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +wlmtk_workspace_t *wlmtk_workspace_create( + wlmtk_env_t *env_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr) +{ + wlmtk_workspace_t *workspace_ptr = + logged_calloc(1, sizeof(wlmtk_workspace_t)); + if (NULL == workspace_ptr) return NULL; + + if (!wlmtk_container_init_attached( + &workspace_ptr->super_container, env_ptr, wlr_scene_tree_ptr)) { + wlmtk_workspace_destroy(workspace_ptr); + return NULL; + } + workspace_ptr->orig_super_element_vmt = wlmtk_element_extend( + &workspace_ptr->super_container.super_element, + &workspace_element_vmt); + + if (!wlmtk_container_init(&workspace_ptr->window_container, env_ptr)) { + wlmtk_workspace_destroy(workspace_ptr); + return NULL; + } + wlmtk_element_set_visible( + &workspace_ptr->window_container.super_element, + true); + wlmtk_container_add_element( + &workspace_ptr->super_container, + &workspace_ptr->window_container.super_element); + + if (!wlmtk_container_init(&workspace_ptr->fullscreen_container, env_ptr)) { + wlmtk_workspace_destroy(workspace_ptr); + return NULL; + } + wlmtk_element_set_visible( + &workspace_ptr->fullscreen_container.super_element, + true); + wlmtk_container_add_element( + &workspace_ptr->super_container, + &workspace_ptr->fullscreen_container.super_element); + + wlmtk_fsm_init(&workspace_ptr->fsm, pfsm_transitions, PFSMS_PASSTHROUGH); + return workspace_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_workspace_destroy(wlmtk_workspace_t *workspace_ptr) +{ + if (NULL != workspace_ptr->fullscreen_container.super_element.parent_container_ptr) { + wlmtk_container_remove_element( + &workspace_ptr->super_container, + &workspace_ptr->fullscreen_container.super_element); + } + wlmtk_container_fini(&workspace_ptr->fullscreen_container); + + if (NULL != workspace_ptr->window_container.super_element.parent_container_ptr) { + wlmtk_container_remove_element( + &workspace_ptr->super_container, + &workspace_ptr->window_container.super_element); + } + wlmtk_container_fini(&workspace_ptr->window_container); + + wlmtk_container_fini(&workspace_ptr->super_container); + free(workspace_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_workspace_set_extents(wlmtk_workspace_t *workspace_ptr, + const struct wlr_box *extents_ptr) +{ + workspace_ptr->x1 = extents_ptr->x; + workspace_ptr->y1 = extents_ptr->y; + workspace_ptr->x2 = extents_ptr->x + extents_ptr->width; + workspace_ptr->y2 = extents_ptr->y + extents_ptr->height; +} + +/* ------------------------------------------------------------------------- */ +struct wlr_box wlmtk_workspace_get_maximize_extents( + wlmtk_workspace_t *workspace_ptr) +{ + // TODO(kaeser@gubbe.ch): Well, actually compute something sensible. + struct wlr_box box = { + .x = workspace_ptr->x1, + .y = workspace_ptr->y1, + .width = workspace_ptr->x2 - workspace_ptr->x1 - 64, + .height = workspace_ptr->y2 - workspace_ptr->y1 - 64 }; + return box; +} + +/* ------------------------------------------------------------------------- */ +struct wlr_box wlmtk_workspace_get_fullscreen_extents( + wlmtk_workspace_t *workspace_ptr) +{ + struct wlr_box box = { + .x = workspace_ptr->x1, + .y = workspace_ptr->y1, + .width = workspace_ptr->x2 - workspace_ptr->x1, + .height = workspace_ptr->y2 - workspace_ptr->y1 }; + return box; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_workspace_map_window(wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr) +{ + wlmtk_element_set_visible(wlmtk_window_element(window_ptr), true); + wlmtk_container_add_element( + &workspace_ptr->window_container, + wlmtk_window_element(window_ptr)); + wlmtk_window_set_workspace(window_ptr, workspace_ptr); + + wlmtk_workspace_activate_window(workspace_ptr, window_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_workspace_unmap_window(wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr) +{ + bool need_activation = false; + + BS_ASSERT(workspace_ptr == wlmtk_window_get_workspace(window_ptr)); + + if (workspace_ptr->grabbed_window_ptr == window_ptr) { + wlmtk_fsm_event(&workspace_ptr->fsm, PFSME_RESET, NULL); + BS_ASSERT(NULL == workspace_ptr->grabbed_window_ptr); + } + + if (workspace_ptr->activated_window_ptr == window_ptr) { + wlmtk_workspace_activate_window(workspace_ptr, NULL); + need_activation = true; + } + + wlmtk_element_set_visible(wlmtk_window_element(window_ptr), false); + + if (wlmtk_window_is_fullscreen(window_ptr)) { + wlmtk_container_remove_element( + &workspace_ptr->fullscreen_container, + wlmtk_window_element(window_ptr)); + } else { + wlmtk_container_remove_element( + &workspace_ptr->window_container, + wlmtk_window_element(window_ptr)); + } + wlmtk_window_set_workspace(window_ptr, NULL); + + if (need_activation) { + // FIXME: What about raising? + bs_dllist_node_t *dlnode_ptr = + workspace_ptr->window_container.elements.head_ptr; + if (NULL != dlnode_ptr) { + wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); + wlmtk_window_t *window_ptr = wlmtk_window_from_element(element_ptr); + wlmtk_workspace_activate_window(workspace_ptr, window_ptr); + } + } +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_workspace_window_to_fullscreen( + wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr, + bool fullscreen) +{ + BS_ASSERT(workspace_ptr == wlmtk_window_get_workspace(window_ptr)); + BS_ASSERT(fullscreen == wlmtk_window_is_fullscreen(window_ptr)); + + if (fullscreen) { + BS_ASSERT( + bs_dllist_contains( + &workspace_ptr->window_container.elements, + wlmtk_dlnode_from_element(wlmtk_window_element(window_ptr)))); + + wlmtk_container_remove_element( + &workspace_ptr->window_container, + wlmtk_window_element(window_ptr)); + wlmtk_container_add_element( + &workspace_ptr->fullscreen_container, + wlmtk_window_element(window_ptr)); + wlmtk_workspace_activate_window(workspace_ptr, window_ptr); + } else { + BS_ASSERT( + bs_dllist_contains( + &workspace_ptr->fullscreen_container.elements, + wlmtk_dlnode_from_element(wlmtk_window_element(window_ptr)))); + + wlmtk_container_remove_element( + &workspace_ptr->fullscreen_container, + wlmtk_window_element(window_ptr)); + wlmtk_container_add_element( + &workspace_ptr->window_container, + wlmtk_window_element(window_ptr)); + wlmtk_workspace_activate_window(workspace_ptr, window_ptr); + } +} + +/* ------------------------------------------------------------------------- */ +bool wlmtk_workspace_motion( + wlmtk_workspace_t *workspace_ptr, + double x, + double y, + uint32_t time_msec) +{ + return wlmtk_element_pointer_motion( + &workspace_ptr->super_container.super_element, x, y, time_msec); +} + +/* ------------------------------------------------------------------------- */ +// TODO(kaeser@gubbe.ch): Improve this, has multiple bugs: It won't keep +// different buttons apart, and there's currently no test associated. +bool wlmtk_workspace_button( + wlmtk_workspace_t *workspace_ptr, + const struct wlr_pointer_button_event *event_ptr) +{ + wlmtk_button_event_t event; + + // Guard clause: nothing to pass on if no element has the focus. + event.button = event_ptr->button; + event.time_msec = event_ptr->time_msec; + if (WLR_BUTTON_PRESSED == event_ptr->state) { + event.type = WLMTK_BUTTON_DOWN; + return wlmtk_element_pointer_button( + &workspace_ptr->super_container.super_element, &event); + + } else if (WLR_BUTTON_RELEASED == event_ptr->state) { + event.type = WLMTK_BUTTON_UP; + wlmtk_element_pointer_button( + &workspace_ptr->super_container.super_element, &event); + event.type = WLMTK_BUTTON_CLICK; + return wlmtk_element_pointer_button( + &workspace_ptr->super_container.super_element, &event); + + } + + bs_log(BS_WARNING, + "Workspace %p: Unhandled state 0x%x for button 0x%x", + workspace_ptr, event_ptr->state, event_ptr->button); + return false; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_workspace_begin_window_move( + wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr) +{ + wlmtk_fsm_event(&workspace_ptr->fsm, PFSME_BEGIN_MOVE, window_ptr); +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_workspace_begin_window_resize( + wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr, + uint32_t edges) +{ + workspace_ptr->resize_edges = edges; + wlmtk_fsm_event(&workspace_ptr->fsm, PFSME_BEGIN_RESIZE, window_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Acticates `window_ptr`. Will de-activate an earlier window. */ +void wlmtk_workspace_activate_window( + wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr) +{ + // Nothing to do. + if (workspace_ptr->activated_window_ptr == window_ptr) return; + + if (NULL != workspace_ptr->activated_window_ptr) { + wlmtk_window_set_activated(workspace_ptr->activated_window_ptr, false); + workspace_ptr->activated_window_ptr = NULL; + } + + if (NULL != window_ptr) { + wlmtk_window_set_activated(window_ptr, true); + workspace_ptr->activated_window_ptr = window_ptr; + } +} + +/* ------------------------------------------------------------------------- */ +wlmtk_window_t *wlmtk_workspace_get_activated_window( + wlmtk_workspace_t *workspace_ptr) +{ + return workspace_ptr->activated_window_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_workspace_raise_window( + wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr) +{ + BS_ASSERT(workspace_ptr == wlmtk_window_get_workspace(window_ptr)); + wlmtk_container_raise_element_to_top(&workspace_ptr->window_container, + wlmtk_window_element(window_ptr)); +} + +/* == Fake workspace methods, useful for tests ============================= */ + +/* ------------------------------------------------------------------------- */ +wlmtk_fake_workspace_t *wlmtk_fake_workspace_create(int width, int height) +{ + wlmtk_fake_workspace_t *fw_ptr = logged_calloc( + 1, sizeof(wlmtk_fake_workspace_t)); + if (NULL == fw_ptr) return NULL; + + fw_ptr->fake_parent_ptr = wlmtk_container_create_fake_parent(); + if (NULL == fw_ptr->fake_parent_ptr) { + wlmtk_fake_workspace_destroy(fw_ptr); + return NULL; + } + + fw_ptr->workspace_ptr = wlmtk_workspace_create( + NULL, fw_ptr->fake_parent_ptr->wlr_scene_tree_ptr); + if (NULL == fw_ptr->workspace_ptr) { + wlmtk_fake_workspace_destroy(fw_ptr); + return NULL; + } + struct wlr_box extents = { .width = width, .height = height }; + wlmtk_workspace_set_extents(fw_ptr->workspace_ptr, &extents); + + return fw_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_fake_workspace_destroy(wlmtk_fake_workspace_t *fw_ptr) +{ + if (NULL != fw_ptr->workspace_ptr) { + wlmtk_workspace_destroy(fw_ptr->workspace_ptr); + fw_ptr->workspace_ptr = NULL; + } + + if (NULL != fw_ptr->fake_parent_ptr) { + wlmtk_container_destroy_fake_parent(fw_ptr->fake_parent_ptr); + fw_ptr->fake_parent_ptr = NULL; + } + + free(fw_ptr); +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** Virtual destructor, wraps to our dtor. */ +void _wlmtk_workspace_element_destroy(wlmtk_element_t *element_ptr) +{ + wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_workspace_t, super_container.super_element); + wlmtk_workspace_destroy(workspace_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Returns the workspace area. */ +void _wlmtk_workspace_element_get_dimensions( + wlmtk_element_t *element_ptr, + int *left_ptr, + int *top_ptr, + int *right_ptr, + int *bottom_ptr) +{ + wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_workspace_t, super_container.super_element); + + if (NULL != left_ptr) *left_ptr = workspace_ptr->x1; + if (NULL != top_ptr) *top_ptr = workspace_ptr->y1; + if (NULL != right_ptr) *right_ptr = workspace_ptr->x2; + if (NULL != bottom_ptr) *bottom_ptr = workspace_ptr->y2; +} + +/* ------------------------------------------------------------------------- */ +/** Returns workspace area: @ref _wlmtk_workspace_element_get_dimensions. */ +void _wlmtk_workspace_element_get_pointer_area( + wlmtk_element_t *element_ptr, + int *x1_ptr, + int *y1_ptr, + int *x2_ptr, + int *y2_ptr) +{ + return _wlmtk_workspace_element_get_dimensions( + element_ptr, x1_ptr, y1_ptr, x2_ptr, y2_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Extends wlmtk_container_t::pointer_button: Feeds the motion into the + * workspace's pointer state machine, and only passes it to the container's + * handler if the event isn't consumed yet. + * + * @param element_ptr + * @param x + * @param y + * @param time_msec + * + * @return Always true. + */ +bool _wlmtk_workspace_element_pointer_motion( + wlmtk_element_t *element_ptr, + double x, double y, + uint32_t time_msec) +{ + wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_workspace_t, super_container.super_element); + + // Note: Workspace ignores the return value. All motion events are whitin. + bool rv = workspace_ptr->orig_super_element_vmt.pointer_motion( + element_ptr, x, y, time_msec); + wlmtk_fsm_event(&workspace_ptr->fsm, PFSME_MOTION, NULL); + + // Focus wasn't claimed, so we'll set the cursor here. + if (!rv) { + wlmtk_env_set_cursor( + workspace_ptr->super_container.super_element.env_ptr, + WLMTK_CURSOR_DEFAULT); + } + + return true; +} + +/* ------------------------------------------------------------------------- */ +/** + * Extends wlmtk_container_t::pointer_button. + * + * @param element_ptr + * @param button_event_ptr + * + * @return Whether the button event was consumed. + */ +bool _wlmtk_workspace_element_pointer_button( + wlmtk_element_t *element_ptr, + const wlmtk_button_event_t *button_event_ptr) +{ + wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_workspace_t, super_container.super_element); + + // TODO(kaeser@gubbe.ch): We should retract as to which event had triggered + // the move, and then figure out the exit condition (button up? key? ...) + // from there. + // See xdg_toplevel::move doc at https://wayland.app/protocols/xdg-shell. + if (button_event_ptr->button == BTN_LEFT && + button_event_ptr->type == WLMTK_BUTTON_UP) { + wlmtk_fsm_event(&workspace_ptr->fsm, PFSME_RELEASED, NULL); + } + + return workspace_ptr->orig_super_element_vmt.pointer_button( + element_ptr, button_event_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Extends wlmtk_container_t::leave. + * + * @param element_ptr + */ +void _wlmtk_workspace_element_pointer_leave( + wlmtk_element_t *element_ptr) +{ + wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_workspace_t, super_container.super_element); + wlmtk_fsm_event(&workspace_ptr->fsm, PFSME_RESET, NULL); + workspace_ptr->orig_super_element_vmt.pointer_leave(element_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Initiates a move. */ +bool pfsm_move_begin(wlmtk_fsm_t *fsm_ptr, void *ud_ptr) +{ + wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( + fsm_ptr, wlmtk_workspace_t, fsm); + + workspace_ptr->grabbed_window_ptr = ud_ptr; + workspace_ptr->motion_x = + workspace_ptr->super_container.super_element.last_pointer_x; + workspace_ptr->motion_y = + workspace_ptr->super_container.super_element.last_pointer_y; + + wlmtk_element_get_position( + wlmtk_window_element(workspace_ptr->grabbed_window_ptr), + &workspace_ptr->initial_x, + &workspace_ptr->initial_y); + + // TODO(kaeser@gubbe.ch): When in move mode, set (and keep) a corresponding + // cursor image. + return true; +} + +/* ------------------------------------------------------------------------- */ +/** Handles motion during a move. */ +bool pfsm_move_motion(wlmtk_fsm_t *fsm_ptr, __UNUSED__ void *ud_ptr) +{ + wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( + fsm_ptr, wlmtk_workspace_t, fsm); + + double rel_x = workspace_ptr->super_container.super_element.last_pointer_x - + workspace_ptr->motion_x; + double rel_y = workspace_ptr->super_container.super_element.last_pointer_y - + workspace_ptr->motion_y; + + wlmtk_window_set_position( + workspace_ptr->grabbed_window_ptr, + workspace_ptr->initial_x + rel_x, + workspace_ptr->initial_y + rel_y); + + return true; +} + +/* ------------------------------------------------------------------------- */ +/** Initiates a resize. */ +bool pfsm_resize_begin(wlmtk_fsm_t *fsm_ptr, void *ud_ptr) +{ + wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( + fsm_ptr, wlmtk_workspace_t, fsm); + + workspace_ptr->grabbed_window_ptr = ud_ptr; + workspace_ptr->motion_x = + workspace_ptr->super_container.super_element.last_pointer_x; + workspace_ptr->motion_y = + workspace_ptr->super_container.super_element.last_pointer_y; + + wlmtk_element_get_position( + wlmtk_window_element(workspace_ptr->grabbed_window_ptr), + &workspace_ptr->initial_x, + &workspace_ptr->initial_y); + + wlmtk_window_get_size( + workspace_ptr->grabbed_window_ptr, + &workspace_ptr->initial_width, + &workspace_ptr->initial_height); + + return true; +} + +/* ------------------------------------------------------------------------- */ +/** Handles motion during a resize. */ +bool pfsm_resize_motion(wlmtk_fsm_t *fsm_ptr, __UNUSED__ void *ud_ptr) +{ + wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( + fsm_ptr, wlmtk_workspace_t, fsm); + + double rel_x = workspace_ptr->super_container.super_element.last_pointer_x - + workspace_ptr->motion_x; + double rel_y = workspace_ptr->super_container.super_element.last_pointer_y - + workspace_ptr->motion_y; + + // Update new boundaries by the relative movement. + int top = workspace_ptr->initial_y; + int bottom = workspace_ptr->initial_y + workspace_ptr->initial_height; + if (workspace_ptr->resize_edges & WLR_EDGE_TOP) { + top += rel_y; + if (top >= bottom) top = bottom - 1; + } else if (workspace_ptr->resize_edges & WLR_EDGE_BOTTOM) { + bottom += rel_y; + if (bottom <= top) bottom = top + 1; + } + + int left = workspace_ptr->initial_x; + int right = workspace_ptr->initial_x + workspace_ptr->initial_width; + if (workspace_ptr->resize_edges & WLR_EDGE_LEFT) { + left += rel_x; + if (left >= right) left = right - 1 ; + } else if (workspace_ptr->resize_edges & WLR_EDGE_RIGHT) { + right += rel_x; + if (right <= left) right = left + 1; + } + + wlmtk_window_request_position_and_size( + workspace_ptr->grabbed_window_ptr, + left, top, + right - left, bottom - top); + return true; +} + +/* ------------------------------------------------------------------------- */ +/** Resets the state machine. */ +bool pfsm_reset(wlmtk_fsm_t *fsm_ptr, __UNUSED__ void *ud_ptr) +{ + wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( + fsm_ptr, wlmtk_workspace_t, fsm); + workspace_ptr->grabbed_window_ptr = NULL; + return true; +} + +/* == Unit tests =========================================================== */ + +static void test_create_destroy(bs_test_t *test_ptr); +static void test_map_unmap(bs_test_t *test_ptr); +static void test_button(bs_test_t *test_ptr); +static void test_move(bs_test_t *test_ptr); +static void test_unmap_during_move(bs_test_t *test_ptr); +static void test_resize(bs_test_t *test_ptr); +static void test_activate(bs_test_t *test_ptr); + +const bs_test_case_t wlmtk_workspace_test_cases[] = { + { 1, "create_destroy", test_create_destroy }, + { 1, "map_unmap", test_map_unmap }, + { 1, "button", test_button }, + { 1, "move", test_move }, + { 1, "unmap_during_move", test_unmap_during_move }, + { 1, "resize", test_resize }, + { 1, "activate", test_activate }, + { 0, NULL, NULL } +}; + +/* ------------------------------------------------------------------------- */ +/** Exercises workspace create & destroy methods. */ +void test_create_destroy(bs_test_t *test_ptr) +{ + wlmtk_container_t *fake_parent_ptr = wlmtk_container_create_fake_parent(); + BS_ASSERT(NULL != fake_parent_ptr); + + wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_create( + NULL, fake_parent_ptr->wlr_scene_tree_ptr); + BS_TEST_VERIFY_NEQ(test_ptr, NULL, workspace_ptr); + + struct wlr_box box = { .x = -10, .y = -20, .width = 100, .height = 200 }; + wlmtk_workspace_set_extents(workspace_ptr, &box); + int x1, y1, x2, y2; + wlmtk_element_get_pointer_area( + &workspace_ptr->super_container.super_element, &x1, &y1, &x2, &y2); + BS_TEST_VERIFY_EQ(test_ptr, -10, x1); + BS_TEST_VERIFY_EQ(test_ptr, -20, y1); + BS_TEST_VERIFY_EQ(test_ptr, 90, x2); + BS_TEST_VERIFY_EQ(test_ptr, 180, y2); + + box = wlmtk_workspace_get_maximize_extents(workspace_ptr); + BS_TEST_VERIFY_EQ(test_ptr, -10, box.x); + BS_TEST_VERIFY_EQ(test_ptr, -20, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 36, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 136, box.height); + + box = wlmtk_workspace_get_fullscreen_extents(workspace_ptr); + BS_TEST_VERIFY_EQ(test_ptr, -10, box.x); + BS_TEST_VERIFY_EQ(test_ptr, -20, box.y); + BS_TEST_VERIFY_EQ(test_ptr, 100, box.width); + BS_TEST_VERIFY_EQ(test_ptr, 200, box.height); + + wlmtk_workspace_destroy(workspace_ptr); + wlmtk_container_destroy_fake_parent(fake_parent_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Verifies that mapping and unmapping windows works. */ +void test_map_unmap(bs_test_t *test_ptr) +{ + wlmtk_fake_workspace_t *fws_ptr = wlmtk_fake_workspace_create(1024, 768); + BS_ASSERT(NULL != fws_ptr); + + wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); + BS_ASSERT(NULL != fw_ptr); + + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_element(fw_ptr->window_ptr)->visible); + wlmtk_workspace_map_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + BS_TEST_VERIFY_NEQ( + test_ptr, + NULL, + wlmtk_window_element(fw_ptr->window_ptr)->wlr_scene_node_ptr); + BS_TEST_VERIFY_NEQ( + test_ptr, + NULL, + fw_ptr->fake_surface_ptr->surface.super_element.wlr_scene_node_ptr); + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_element(fw_ptr->window_ptr)->visible); + + wlmtk_workspace_unmap_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ( + test_ptr, + NULL, + wlmtk_window_element(fw_ptr->window_ptr)->wlr_scene_node_ptr); + BS_TEST_VERIFY_EQ( + test_ptr, + NULL, + fw_ptr->fake_surface_ptr->surface.super_element.wlr_scene_node_ptr); + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_element(fw_ptr->window_ptr)->visible); + + wlmtk_fake_window_destroy(fw_ptr); + wlmtk_fake_workspace_destroy(fws_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests wlmtk_workspace_button. */ +void test_button(bs_test_t *test_ptr) +{ + wlmtk_fake_workspace_t *fws_ptr = wlmtk_fake_workspace_create(1024, 768); + BS_ASSERT(NULL != fws_ptr); + wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); + wlmtk_element_set_visible(&fake_element_ptr->element, true); + BS_ASSERT(NULL != fake_element_ptr); + + wlmtk_container_add_element( + &fws_ptr->workspace_ptr->super_container, &fake_element_ptr->element); + + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_workspace_motion(fws_ptr->workspace_ptr, 0, 0, 1234)); + BS_TEST_VERIFY_TRUE( + test_ptr, + fake_element_ptr->pointer_motion_called); + + // Verify that a button down event is passed. + struct wlr_pointer_button_event wlr_pointer_button_event = { + .button = 42, + .state = WLR_BUTTON_PRESSED, + .time_msec = 4321, + }; + wlmtk_workspace_button(fws_ptr->workspace_ptr, &wlr_pointer_button_event); + wlmtk_button_event_t expected_event = { + .button = 42, + .type = WLMTK_BUTTON_DOWN, + .time_msec = 4321, + }; + BS_TEST_VERIFY_MEMEQ( + test_ptr, + &expected_event, + &fake_element_ptr->pointer_button_event, + sizeof(wlmtk_button_event_t)); + + // The button up event should trigger a click. + wlr_pointer_button_event.state = WLR_BUTTON_RELEASED; + wlmtk_workspace_button(fws_ptr->workspace_ptr, &wlr_pointer_button_event); + expected_event.type = WLMTK_BUTTON_CLICK; + BS_TEST_VERIFY_MEMEQ( + test_ptr, + &expected_event, + &fake_element_ptr->pointer_button_event, + sizeof(wlmtk_button_event_t)); + + wlmtk_container_remove_element( + &fws_ptr->workspace_ptr->super_container, &fake_element_ptr->element); + + wlmtk_element_destroy(&fake_element_ptr->element); + wlmtk_fake_workspace_destroy(fws_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests moving a window. */ +void test_move(bs_test_t *test_ptr) +{ + wlmtk_fake_workspace_t *fws_ptr = wlmtk_fake_workspace_create(1024, 768); + BS_ASSERT(NULL != fws_ptr); + wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); + BS_ASSERT(NULL != fw_ptr); + + wlmtk_workspace_motion(fws_ptr->workspace_ptr, 0, 0, 42); + + wlmtk_workspace_map_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(fw_ptr->window_ptr)->x); + BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(fw_ptr->window_ptr)->y); + + // Starts a move for the window. Will move it... + wlmtk_workspace_begin_window_move(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + wlmtk_workspace_motion(fws_ptr->workspace_ptr, 1, 2, 43); + BS_TEST_VERIFY_EQ(test_ptr, 1, wlmtk_window_element(fw_ptr->window_ptr)->x); + BS_TEST_VERIFY_EQ(test_ptr, 2, wlmtk_window_element(fw_ptr->window_ptr)->y); + + // Releases the button. Should end the move. + struct wlr_pointer_button_event wlr_pointer_button_event = { + .button = BTN_LEFT, + .state = WLR_BUTTON_RELEASED, + .time_msec = 44, + }; + wlmtk_workspace_button(fws_ptr->workspace_ptr, &wlr_pointer_button_event); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fws_ptr->workspace_ptr->grabbed_window_ptr); + + // More motion, no longer updates the position. + wlmtk_workspace_motion(fws_ptr->workspace_ptr, 3, 4, 45); + BS_TEST_VERIFY_EQ(test_ptr, 1, wlmtk_window_element(fw_ptr->window_ptr)->x); + BS_TEST_VERIFY_EQ(test_ptr, 2, wlmtk_window_element(fw_ptr->window_ptr)->y); + + wlmtk_workspace_unmap_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + + wlmtk_fake_window_destroy(fw_ptr); + wlmtk_fake_workspace_destroy(fws_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests moving a window that unmaps during the move. */ +void test_unmap_during_move(bs_test_t *test_ptr) +{ + wlmtk_fake_workspace_t *fws_ptr = wlmtk_fake_workspace_create(1024, 768); + BS_ASSERT(NULL != fws_ptr); + wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); + BS_ASSERT(NULL != fw_ptr); + + wlmtk_workspace_motion(fws_ptr->workspace_ptr, 0, 0, 42); + + wlmtk_workspace_map_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(fw_ptr->window_ptr)->x); + BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(fw_ptr->window_ptr)->y); + + // Starts a move for the window. Will move it... + wlmtk_workspace_begin_window_move(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + wlmtk_workspace_motion(fws_ptr->workspace_ptr, 1, 2, 43); + BS_TEST_VERIFY_EQ(test_ptr, 1, wlmtk_window_element(fw_ptr->window_ptr)->x); + BS_TEST_VERIFY_EQ(test_ptr, 2, wlmtk_window_element(fw_ptr->window_ptr)->y); + + wlmtk_workspace_unmap_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fws_ptr->workspace_ptr->grabbed_window_ptr); + + // More motion, no longer updates the position. + wlmtk_workspace_motion(fws_ptr->workspace_ptr, 3, 4, 45); + BS_TEST_VERIFY_EQ(test_ptr, 1, wlmtk_window_element(fw_ptr->window_ptr)->x); + BS_TEST_VERIFY_EQ(test_ptr, 2, wlmtk_window_element(fw_ptr->window_ptr)->y); + + + // More motion, no longer updates the position. + wlmtk_workspace_motion(fws_ptr->workspace_ptr, 3, 4, 45); + BS_TEST_VERIFY_EQ(test_ptr, 1, wlmtk_window_element(fw_ptr->window_ptr)->x); + BS_TEST_VERIFY_EQ(test_ptr, 2, wlmtk_window_element(fw_ptr->window_ptr)->y); + + wlmtk_fake_window_destroy(fw_ptr); + wlmtk_fake_workspace_destroy(fws_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests resizing a window. */ +void test_resize(bs_test_t *test_ptr) +{ + wlmtk_fake_workspace_t *fws_ptr = wlmtk_fake_workspace_create(1024, 768); + BS_ASSERT(NULL != fws_ptr); + + wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); + BS_ASSERT(NULL != fw_ptr); + wlmtk_window_request_position_and_size(fw_ptr->window_ptr, 0, 0, 40, 20); + wlmtk_fake_window_commit_size(fw_ptr); + wlmtk_workspace_motion(fws_ptr->workspace_ptr, 0, 0, 42); + + wlmtk_workspace_map_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(fw_ptr->window_ptr)->x); + BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(fw_ptr->window_ptr)->y); + int width, height; + wlmtk_window_get_size(fw_ptr->window_ptr, &width, &height); + BS_TEST_VERIFY_EQ(test_ptr, 40, width); + BS_TEST_VERIFY_EQ(test_ptr, 20, height); + + // Starts a resize for the window. Will resize & move it... + wlmtk_workspace_begin_window_resize( + fws_ptr->workspace_ptr, fw_ptr->window_ptr, WLR_EDGE_TOP | WLR_EDGE_LEFT); + fw_ptr->fake_surface_ptr->serial = 1; // The serial. + wlmtk_workspace_motion(fws_ptr->workspace_ptr, 1, 2, 43); + BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(fw_ptr->window_ptr)->x); + BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(fw_ptr->window_ptr)->y); + BS_TEST_VERIFY_EQ(test_ptr, 37, fw_ptr->fake_surface_ptr->requested_width); + BS_TEST_VERIFY_EQ(test_ptr, 16, fw_ptr->fake_surface_ptr->requested_height); + // This updates for the given serial. + wlmtk_fake_window_commit_size(fw_ptr); + wlmtk_window_get_size(fw_ptr->window_ptr, &width, &height); + BS_TEST_VERIFY_EQ(test_ptr, 1, wlmtk_window_element(fw_ptr->window_ptr)->x); + BS_TEST_VERIFY_EQ(test_ptr, 2, wlmtk_window_element(fw_ptr->window_ptr)->y); + BS_TEST_VERIFY_EQ(test_ptr, 39, width); + BS_TEST_VERIFY_EQ(test_ptr, 18, height); + + // Releases the button. Should end the move. + struct wlr_pointer_button_event wlr_pointer_button_event = { + .button = BTN_LEFT, + .state = WLR_BUTTON_RELEASED, + .time_msec = 44, + }; + wlmtk_workspace_button(fws_ptr->workspace_ptr, &wlr_pointer_button_event); + BS_TEST_VERIFY_EQ(test_ptr, NULL, fws_ptr->workspace_ptr->grabbed_window_ptr); + + wlmtk_workspace_unmap_window(fws_ptr->workspace_ptr, fw_ptr->window_ptr); + wlmtk_fake_window_destroy(fw_ptr); + wlmtk_fake_workspace_destroy(fws_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Tests window activation. */ +void test_activate(bs_test_t *test_ptr) +{ + wlmtk_fake_workspace_t *fws_ptr = wlmtk_fake_workspace_create(1024, 768); + BS_ASSERT(NULL != fws_ptr); + + // Window 1: from (0, 0) to (100, 100) + wlmtk_fake_window_t *fw1_ptr = wlmtk_fake_window_create(); + wlmtk_surface_request_size(&fw1_ptr->fake_surface_ptr->surface, 100, 100); + wlmtk_fake_window_commit_size(fw1_ptr); + wlmtk_window_set_position(fw1_ptr->window_ptr, 0, 0); + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_window_is_activated(fw1_ptr->window_ptr)); + + // Window 1 is mapped => it's activated. + wlmtk_workspace_map_window(fws_ptr->workspace_ptr, fw1_ptr->window_ptr); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_window_is_activated(fw1_ptr->window_ptr)); + + // Window 2: from (200, 0) to (300, 100). + // Window 2 is mapped: Will get activated, and 1st one de-activated. + wlmtk_fake_window_t *fw2_ptr = wlmtk_fake_window_create(); + wlmtk_surface_request_size(&fw2_ptr->fake_surface_ptr->surface, 100, 100); + wlmtk_fake_window_commit_size(fw2_ptr); + wlmtk_window_set_position(fw2_ptr->window_ptr, 200, 0); + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_window_is_activated(fw2_ptr->window_ptr)); + wlmtk_workspace_map_window(fws_ptr->workspace_ptr, fw2_ptr->window_ptr); + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_window_is_activated(fw1_ptr->window_ptr)); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_window_is_activated(fw2_ptr->window_ptr)); + + // Pointer move, over window 1. Nothing happens: We have click-to-focus. + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_workspace_motion(fws_ptr->workspace_ptr, 50, 50, 0)); + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_window_is_activated(fw1_ptr->window_ptr)); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_window_is_activated(fw2_ptr->window_ptr)); + + // Click on window 1: Gets activated. + struct wlr_pointer_button_event wlr_button_event = { + .button = BTN_RIGHT, .state = WLR_BUTTON_PRESSED + }; + wlmtk_workspace_button(fws_ptr->workspace_ptr, &wlr_button_event); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_window_is_activated(fw1_ptr->window_ptr)); + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_window_is_activated(fw2_ptr->window_ptr)); + + // Unmap window 1. Now window 2 gets activated. + wlmtk_workspace_unmap_window(fws_ptr->workspace_ptr, fw1_ptr->window_ptr); + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_window_is_activated(fw1_ptr->window_ptr)); + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_window_is_activated(fw2_ptr->window_ptr)); + + // Unmap the remaining window 2. Nothing is activated. + wlmtk_workspace_unmap_window(fws_ptr->workspace_ptr, fw2_ptr->window_ptr); + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_window_is_activated(fw2_ptr->window_ptr)); + + wlmtk_fake_window_destroy(fw2_ptr); + wlmtk_fake_window_destroy(fw1_ptr); + wlmtk_fake_workspace_destroy(fws_ptr); +} + +/* == End of workspace.c =================================================== */ diff --git a/src/toolkit/workspace.h b/src/toolkit/workspace.h new file mode 100644 index 00000000..6676c477 --- /dev/null +++ b/src/toolkit/workspace.h @@ -0,0 +1,219 @@ +/* ========================================================================= */ +/** + * @file workspace.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_WORKSPACE_H__ +#define __WLMTK_WORKSPACE_H__ + +/** State of the workspace. */ +typedef struct _wlmtk_workspace_t wlmtk_workspace_t; + +#include "container.h" +#include "window.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Forward declaration. */ +struct wlr_pointer_button_event; +/** Forward declaration. */ +struct wlr_box; + +/** + * Creates a workspace. + * + * TODO(kaeser@gubbe.ch): Consider replacing the interface with a container, + * and permit a "toplevel" container that will be at the server level. + * + * @param env_ptr + * @param wlr_scene_tree_ptr + * + * @return Pointer to the workspace state, or NULL on error. Must be free'd + * via @ref wlmtk_workspace_destroy. + */ +wlmtk_workspace_t *wlmtk_workspace_create( + wlmtk_env_t *env_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr); + +/** + * Destroys the workspace. Will destroy any stil-contained element. + * + * @param workspace_ptr + */ +void wlmtk_workspace_destroy(wlmtk_workspace_t *workspace_ptr); + +/** + * Sets (or updates) the extents of the workspace. + * + * @param workspace_ptr + * @param extents_ptr + */ +void wlmtk_workspace_set_extents(wlmtk_workspace_t *workspace_ptr, + const struct wlr_box *extents_ptr); + +/** + * Returns the extents of the workspace available for maximized windows. + * + * @param workspace_ptr + * + * @return A `struct wlr_box` that lines out the available space and position. + */ +struct wlr_box wlmtk_workspace_get_maximize_extents( + wlmtk_workspace_t *workspace_ptr); + +/** + * Returns the extents of the workspace available for fullscreen windows. + * + * @param workspace_ptr + * + * @return A `struct wlr_box` that lines out the available space and position. + */ +struct wlr_box wlmtk_workspace_get_fullscreen_extents( + wlmtk_workspace_t *workspace_ptr); + +/** + * Maps the window: Adds it to the workspace container and makes it visible. + * + * @param workspace_ptr + * @param window_ptr + */ +void wlmtk_workspace_map_window(wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr); + +/** + * Unmaps the window: Sets it as invisible and removes it from the container. + * + * @param workspace_ptr + * @param window_ptr + */ +void wlmtk_workspace_unmap_window(wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr); + +/** + * Promotes the window to the fullscreen layer (or back). + * + * To be called by @ref wlmtk_window_commit_fullscreen. + * + * @param workspace_ptr + * @param window_ptr + * @param fullscreen + */ +void wlmtk_workspace_window_to_fullscreen( + wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr, + bool fullscreen); + +/** + * Handles a motion event. + * + * TODO(kaeser@gubbe.ch): Move this to the server, and have the workspace's + * motion handling dealt with the element's pointer_motion method. + * + * @param workspace_ptr + * @param x + * @param y + * @param time_msec + * + * @return Whether there was an element under the pointer. + */ +bool wlmtk_workspace_motion( + wlmtk_workspace_t *workspace_ptr, + double x, + double y, + uint32_t time_msec); + +/** + * Handles a button event: Translates to button down/up/click/dblclick events. + * + * Each button activity (button pressed or released) will directly trigger a + * corresponding BUTTON_DOWN or BUTTON_UP event. Depending on timing and + * motion, a "released" event may also triccer a CLICK, DOUBLE_CLICK or + * DRAG event. + * These events will be forwarded to the event currently having pointer focus. + * + * TODO(kaeser@gubbe.ch): Implement DOUBLE_CLICK and DRAG events. Also, move + * this code into the server and make it well tested. + * + * @param workspace_ptr + * @param event_ptr + * + * @return Whether the button was consumed. + */ +bool wlmtk_workspace_button( + wlmtk_workspace_t *workspace_ptr, + const struct wlr_pointer_button_event *event_ptr); + +/** + * Initiates a 'move' for the window. + * + * @param workspace_ptr + * @param window_ptr + */ +void wlmtk_workspace_begin_window_move( + wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr); + +/** + * Initiates a 'resize' for the window. + * + * @param workspace_ptr + * @param window_ptr + * @param edges + */ +void wlmtk_workspace_begin_window_resize( + wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr, + uint32_t edges); + +/** Acticates `window_ptr`. Will de-activate an earlier window. */ +void wlmtk_workspace_activate_window( + wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr); + +/** @return Pointer to the activated @ref wlmtk_window_t, if any. */ +wlmtk_window_t *wlmtk_workspace_get_activated_window( + wlmtk_workspace_t *workspace_ptr); + +/** Raises `window_ptr`: Will show it atop all other windows. */ +void wlmtk_workspace_raise_window( + wlmtk_workspace_t *workspace_ptr, + wlmtk_window_t *window_ptr); + +/** Fake workspace: A real workspace, but with a fake parent. For testing. */ +typedef struct { + /** The workspace. */ + wlmtk_workspace_t *workspace_ptr; + /** The (fake) parent container. */ + wlmtk_container_t *fake_parent_ptr; +} wlmtk_fake_workspace_t; + +/** Creates a fake workspace with specified extents. */ +wlmtk_fake_workspace_t *wlmtk_fake_workspace_create(int width, int height); +/** Destroys the fake workspace. */ +void wlmtk_fake_workspace_destroy(wlmtk_fake_workspace_t *fake_workspace_ptr); + +/** Unit tests for the workspace. */ +extern const bs_test_case_t wlmtk_workspace_test_cases[]; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_WORKSPACE_H__ */ +/* == End of workspace.h =================================================== */ diff --git a/src/view.c b/src/view.c index ed3c8b41..ea8b270e 100644 --- a/src/view.c +++ b/src/view.c @@ -24,9 +24,7 @@ #include "config.h" #include "decorations.h" #include "menu.h" -#include "resizebar.h" -#include "titlebar.h" -#include "util.h" +#include "toolkit/toolkit.h" #include @@ -51,8 +49,6 @@ static void window_menu_callback_move_to_workspace1(void *ud_ptr); static void window_menu_callback_move_to_workspace2(void *ud_ptr); static void window_menu_callback_close(void *ud_ptr); -static void wlmaker_view_apply_decoration(wlmaker_view_t *view_ptr); - /* == Data ================================================================= */ /** Descriptors for the menu entries of the view's "Window menu". */ @@ -110,7 +106,7 @@ void wlmaker_view_init( wlmaker_interactive_node_destroy); BS_ASSERT(view_ptr->interactive_tree_ptr); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &view_ptr->server_ptr->cursor_ptr->button_release_event, &view_ptr->button_release_listener, handle_button_release); @@ -154,11 +150,6 @@ void wlmaker_view_fini(wlmaker_view_t *view_ptr) view_ptr->interactive_tree_ptr = NULL; } - if (NULL != view_ptr->window_decorations_ptr) { - wlmaker_window_decorations_destroy(view_ptr->window_decorations_ptr); - view_ptr->window_decorations_ptr = NULL; - } - if (NULL != view_ptr->elements_wlr_scene_tree_ptr) { wlr_scene_node_destroy(&view_ptr->elements_wlr_scene_tree_ptr->node); view_ptr->elements_wlr_scene_tree_ptr = NULL; @@ -275,8 +266,9 @@ wlmaker_view_t *wlmaker_view_at( NULL == wlr_scene_tree_ptr->node.data) { wlr_scene_tree_ptr = wlr_scene_tree_ptr->node.parent; } - return (wlmaker_view_t*)wlr_scene_tree_ptr->node.data; + if (NULL == wlr_scene_tree_ptr) return NULL; + return (wlmaker_view_t*)wlr_scene_tree_ptr->node.data; } /* ------------------------------------------------------------------------- */ @@ -338,17 +330,6 @@ void wlmaker_view_handle_axis( } } -/* ------------------------------------------------------------------------- */ -void wlmaker_view_set_server_side_decoration( - wlmaker_view_t *view_ptr, - bool enabled) -{ - // Don't act if there's nothing to do... - if (view_ptr->server_side_decoration_enabled == enabled) return; - view_ptr->server_side_decoration_enabled = enabled; - wlmaker_view_apply_decoration(view_ptr); -} - /* ------------------------------------------------------------------------- */ void wlmaker_view_window_menu_show(wlmaker_view_t *view_ptr) { @@ -443,21 +424,10 @@ void wlmaker_view_cursor_leave(wlmaker_view_t *view_ptr) } /* ------------------------------------------------------------------------- */ -void wlmaker_view_shade(wlmaker_view_t *view_ptr) +void wlmaker_view_shade(__UNUSED__ wlmaker_view_t *view_ptr) { - if (!view_ptr->server_side_decorated) { - bs_log(BS_INFO, "Shade only available when server-side-decorated."); - return; - } - BS_ASSERT(NULL != view_ptr->view_wlr_scene_tree_ptr); - - view_ptr->shaded ^= true; - wlr_scene_node_set_enabled( - &view_ptr->view_wlr_scene_tree_ptr->node, !view_ptr->shaded); - if (NULL != view_ptr->window_decorations_ptr) { - wlmaker_window_decorations_set_shade( - view_ptr->window_decorations_ptr, view_ptr->shaded); - } + bs_log(BS_INFO, "Shade only available when server-side-decorated."); + return; } /* ------------------------------------------------------------------------- */ @@ -466,19 +436,6 @@ void wlmaker_view_get_size(wlmaker_view_t *view_ptr, uint32_t *height_ptr) { view_ptr->impl_ptr->get_size(view_ptr, width_ptr, height_ptr); - if (view_ptr->server_side_decorated) { - if (NULL != view_ptr->window_decorations_ptr) { - uint32_t deco_width, deco_height; - wlmaker_window_decorations_get_added_size( - view_ptr->window_decorations_ptr, &deco_width, &deco_height); - if (NULL != width_ptr) { - *width_ptr += deco_width; - } - if (NULL != height_ptr) { - *height_ptr += deco_height; - } - } - } } /* ------------------------------------------------------------------------- */ @@ -487,21 +444,6 @@ void wlmaker_view_set_size(wlmaker_view_t *view_ptr, int width, int height) width = BS_MAX(1, width); height = BS_MAX(1, height); - if (view_ptr->server_side_decorated) { - BS_ASSERT(NULL != view_ptr->window_decorations_ptr); - - uint32_t deco_width, deco_height; - wlmaker_window_decorations_get_added_size( - view_ptr->window_decorations_ptr, &deco_width, &deco_height); - width -= deco_width; - height -= deco_height; - - width = BS_MAX(1, width); - height = BS_MAX(1, height); - - wlmaker_window_decorations_set_inner_size( - view_ptr->window_decorations_ptr, width, height); - } view_ptr->impl_ptr->set_size(view_ptr, width, height); } @@ -511,30 +453,12 @@ void wlmaker_view_get_position(wlmaker_view_t *view_ptr, { *x_ptr = view_ptr->elements_wlr_scene_tree_ptr->node.x; *y_ptr = view_ptr->elements_wlr_scene_tree_ptr->node.y; - - if (NULL != view_ptr->window_decorations_ptr) { - int relx, rely; - wlmaker_window_decorations_relative_position( - view_ptr->window_decorations_ptr, &relx, &rely); - *x_ptr += relx; - *y_ptr += rely; - } } /* ------------------------------------------------------------------------- */ void wlmaker_view_set_position(wlmaker_view_t *view_ptr, int x, int y) { - if (NULL != view_ptr->window_decorations_ptr) { - // Adjust position by the top-left decoration elements, since the node - // position does not factor in these. - int relx, rely; - wlmaker_window_decorations_relative_position( - view_ptr->window_decorations_ptr, &relx, &rely); - x -= relx; - y -= rely; - } - if (x != view_ptr->elements_wlr_scene_tree_ptr->node.x || y != view_ptr->elements_wlr_scene_tree_ptr->node.y) { wlr_scene_node_set_position( @@ -611,7 +535,6 @@ void wlmaker_view_set_fullscreen(wlmaker_view_t *view_ptr, bool fullscreen) wlmaker_workspace_demote_view_from_fullscreen( view_ptr->workspace_ptr, view_ptr); } - wlmaker_view_apply_decoration(view_ptr); wlmaker_view_set_position(view_ptr, new_box.x, new_box.y); wlmaker_view_set_size(view_ptr, new_box.width, new_box.height); @@ -645,11 +568,6 @@ void wlmaker_view_set_title(wlmaker_view_t *view_ptr, const char *title_ptr) if (NULL != title_ptr) { view_ptr->title_ptr = logged_strdup(title_ptr); } - - if (NULL != view_ptr->window_decorations_ptr) { - wlmaker_window_decorations_update_title( - view_ptr->window_decorations_ptr); - } } /* ------------------------------------------------------------------------- */ @@ -691,7 +609,6 @@ void wlmaker_view_map(wlmaker_view_t *view_ptr, view_ptr->workspace_ptr, view_ptr, layer); - wlmaker_view_apply_decoration(view_ptr); wl_signal_emit(&view_ptr->server_ptr->view_mapped_event, view_ptr); } @@ -702,9 +619,6 @@ void wlmaker_view_unmap(wlmaker_view_t *view_ptr) BS_ASSERT(NULL != view_ptr->workspace_ptr); // Should be mapped. wlmaker_workspace_remove_view(view_ptr->workspace_ptr, view_ptr); view_ptr->workspace_ptr = NULL; - wlmaker_view_apply_decoration(view_ptr); - - wlmaker_cursor_unmap_view(view_ptr->server_ptr->cursor_ptr, view_ptr); wl_signal_emit(&view_ptr->server_ptr->view_unmapped_event, view_ptr); } @@ -897,44 +811,4 @@ void window_menu_callback_close(void *ud_ptr) view_ptr->send_close_callback(view_ptr); } -/* ------------------------------------------------------------------------- */ -/** - * Apply server-side decoration, if view is configured and in suitable state. - * - * Will set `wlmaker_view_t.server_side_decorated` suitably. - * - * @param view_ptr - */ -void wlmaker_view_apply_decoration(wlmaker_view_t *view_ptr) -{ - // We won't have the view decorated if it's (1) disabled, (2) unmapped, - // or (3) currently in fullscreen mode. - if ((!view_ptr->server_side_decoration_enabled) || - (NULL == view_ptr->workspace_ptr) || - view_ptr->fullscreen) { - - if (NULL != view_ptr->window_decorations_ptr) { - wlmaker_window_decorations_destroy( - view_ptr->window_decorations_ptr); - view_ptr->window_decorations_ptr = NULL; - } - view_ptr->server_side_decorated = false; - - return; - } - - // Store, then later: re-adjust position of the view. - int pos_x, pos_y; - wlmaker_view_get_position(view_ptr, &pos_x, &pos_y); - - // Well: Decoration is enabled, we're mapped, and not in fullscreen. There - // should be decoration elements... - view_ptr->window_decorations_ptr = wlmaker_window_decorations_create( - view_ptr); - BS_ASSERT(NULL != view_ptr->window_decorations_ptr); - view_ptr->server_side_decorated = true; - - wlmaker_view_set_position(view_ptr, pos_x, pos_y); -} - /* == End of view.c ======================================================== */ diff --git a/src/view.h b/src/view.h index 896d0069..30e33a77 100644 --- a/src/view.h +++ b/src/view.h @@ -63,8 +63,6 @@ typedef struct _wlmaker_view_t wlmaker_view_t; #include "server.h" #include "workspace.h" -#include "decorations/window_decorations.h" - #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -194,33 +192,6 @@ struct _wlmaker_view_t { /** Anchor of the view. */ uint32_t anchor; - /** - * Whether this view is configured to have server-side decoration. - * - * This value conforms to the result of the protocol negotiation of the - * "XDG Decoration" extension. The decoration may be enabled, but - * currently not visible -- for example, if the view is not mapped, or - * if the view is in fullscreen mode. - * - * See @ref server_side_decorated. - */ - bool server_side_decoration_enabled; - /** - * Whether this view is currently server-side decorated. - * - * Is true iff server-side decoration elements are currently visible. - * Implies that @ref server_side_decoration_enabled, that the view is - * currently mapped and not in fullscreen mode. - */ - bool server_side_decorated; - - /** - * All window decorations. Will be set only if decorations are enabled - * (@ref server_side_decoration_enabled), the view is mapped, - * and not currently in fullscreen mode. - */ - wlmaker_window_decorations_t *window_decorations_ptr; - /** Whether this view is currently active (focussed). */ bool active; /** @@ -407,19 +378,6 @@ void wlmaker_view_handle_axis( double y, struct wlr_pointer_axis_event *event_ptr); -/** - * Enables, respectively disables server-side decoration for the view. - * - * If the view is already mapped, this will trigger creation of the decoration - * elements. Otherwise, elements will be created when the view gets mapped. - * - * @param view_ptr - * @param enabled - */ -void wlmaker_view_set_server_side_decoration( - wlmaker_view_t *view_ptr, - bool enabled); - /** * Shows the "window menu" for this view. * diff --git a/src/wlmaker.c b/src/wlmaker.c index 7ed03cc1..c8c4d2f3 100644 --- a/src/wlmaker.c +++ b/src/wlmaker.c @@ -16,12 +16,6 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * - * In the parent directory: - * - * * meson build/ --reconfigure - * * ninja -C build - * * doxygen */ /// setenv() is a POSIX extension. @@ -157,10 +151,23 @@ void toggle_fullscreen(wlmaker_server_t *server_ptr, __UNUSED__ void *arg_ptr) { wlmaker_workspace_t *workspace_ptr = wlmaker_server_get_current_workspace( server_ptr); - wlmaker_view_t *view_ptr = wlmaker_workspace_get_activated_view( + + wlmtk_workspace_t *wlmtk_workspace_ptr = wlmaker_workspace_wlmtk( workspace_ptr); - if (NULL == view_ptr) return; // No activated view, nothing to do. - wlmaker_view_set_fullscreen(view_ptr, !view_ptr->fullscreen); + if (NULL != wlmtk_workspace_ptr) { + + wlmtk_window_t *window_ptr = wlmtk_workspace_get_activated_window( + wlmtk_workspace_ptr); + if (NULL == window_ptr) return; + wlmtk_window_request_fullscreen( + window_ptr, !wlmtk_window_is_fullscreen(window_ptr)); + + } else { + wlmaker_view_t *view_ptr = wlmaker_workspace_get_activated_view( + workspace_ptr); + if (NULL == view_ptr) return; // No activated view, nothing to do. + wlmaker_view_set_fullscreen(view_ptr, !view_ptr->fullscreen); + } } /* ------------------------------------------------------------------------- */ @@ -169,10 +176,23 @@ void toggle_maximize(wlmaker_server_t *server_ptr, __UNUSED__ void *arg_ptr) { wlmaker_workspace_t *workspace_ptr = wlmaker_server_get_current_workspace( server_ptr); - wlmaker_view_t *view_ptr = wlmaker_workspace_get_activated_view( + + wlmtk_workspace_t *wlmtk_workspace_ptr = wlmaker_workspace_wlmtk( workspace_ptr); - if (NULL == view_ptr) return; // No activated view, nothing to do. - wlmaker_view_set_maximized(view_ptr, !view_ptr->maximized); + if (NULL != wlmtk_workspace_ptr) { + + wlmtk_window_t *window_ptr = wlmtk_workspace_get_activated_window( + wlmtk_workspace_ptr); + if (NULL == window_ptr) return; + wlmtk_window_request_maximized( + window_ptr, !wlmtk_window_is_maximized(window_ptr)); + + } else { + wlmaker_view_t *view_ptr = wlmaker_workspace_get_activated_view( + workspace_ptr); + if (NULL == view_ptr) return; // No activated view, nothing to do. + wlmaker_view_set_maximized(view_ptr, !view_ptr->maximized); + } } /* == Main program ========================================================= */ diff --git a/src/wlmtk_xdg_popup.c b/src/wlmtk_xdg_popup.c new file mode 100644 index 00000000..87ffc131 --- /dev/null +++ b/src/wlmtk_xdg_popup.c @@ -0,0 +1,186 @@ +/* ========================================================================= */ +/** + * @file wlmtk_xdg_popup.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wlmtk_xdg_popup.h" + +#define WLR_USE_UNSTABLE +#include +#undef WLR_USE_UNSTABLE + +/* == Declarations ========================================================= */ + +static struct wlr_scene_node *_wlmtk_xdg_popup_surface_element_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr); + +/** Virtual methods for XDG popup surface, for the Element superclass. */ +const wlmtk_element_vmt_t _wlmtk_xdg_popup_surface_element_vmt = { + .create_scene_node = _wlmtk_xdg_popup_surface_element_create_scene_node, +}; + +static void handle_reposition( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_destroy( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_new_popup( + struct wl_listener *listener_ptr, + void *data_ptr); + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +wlmtk_xdg_popup_t *wlmtk_xdg_popup_create( + struct wlr_xdg_popup *wlr_xdg_popup_ptr, + wlmtk_env_t *env_ptr) +{ + wlmtk_xdg_popup_t *xdg_popup_ptr = logged_calloc( + 1, sizeof(wlmtk_xdg_popup_t)); + if (NULL == xdg_popup_ptr) return NULL; + xdg_popup_ptr->wlr_xdg_popup_ptr = wlr_xdg_popup_ptr; + + if (!wlmtk_surface_init( + &xdg_popup_ptr->surface, + wlr_xdg_popup_ptr->base->surface, + env_ptr)) { + wlmtk_xdg_popup_destroy(xdg_popup_ptr); + return NULL; + } + wlmtk_element_extend( + &xdg_popup_ptr->surface.super_element, + &_wlmtk_xdg_popup_surface_element_vmt); + + if (!wlmtk_content_init( + &xdg_popup_ptr->super_content, + &xdg_popup_ptr->surface, + env_ptr)) { + wlmtk_xdg_popup_destroy(xdg_popup_ptr); + return NULL; + } + + wlmtk_util_connect_listener_signal( + &wlr_xdg_popup_ptr->events.reposition, + &xdg_popup_ptr->reposition_listener, + handle_reposition); + + wlmtk_util_connect_listener_signal( + &wlr_xdg_popup_ptr->base->events.destroy, + &xdg_popup_ptr->destroy_listener, + handle_destroy); + wlmtk_util_connect_listener_signal( + &wlr_xdg_popup_ptr->base->events.new_popup, + &xdg_popup_ptr->new_popup_listener, + handle_new_popup); + + return xdg_popup_ptr; +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_xdg_popup_destroy(wlmtk_xdg_popup_t *xdg_popup_ptr) +{ + wl_list_remove(&xdg_popup_ptr->new_popup_listener.link); + wl_list_remove(&xdg_popup_ptr->destroy_listener.link); + wl_list_remove(&xdg_popup_ptr->reposition_listener.link); + + wlmtk_content_fini(&xdg_popup_ptr->super_content); + wlmtk_surface_fini(&xdg_popup_ptr->surface); + free(xdg_popup_ptr); +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +/** Implements @ref wlmtk_element_vmt_t::create_scene_node. Create node. */ +struct wlr_scene_node *_wlmtk_xdg_popup_surface_element_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr) +{ + wlmtk_xdg_popup_t *xdg_popup_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_xdg_popup_t, surface.super_element); + + struct wlr_scene_tree *surface_wlr_scene_tree_ptr = + wlr_scene_xdg_surface_create( + wlr_scene_tree_ptr, + xdg_popup_ptr->wlr_xdg_popup_ptr->base); + return &surface_wlr_scene_tree_ptr->node; +} + +/* ------------------------------------------------------------------------- */ +/** Handles repositioning. Yet unimplemented. */ +void handle_reposition( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_popup_t *xdg_popup_ptr = BS_CONTAINER_OF( + listener_ptr, wlmtk_xdg_popup_t, reposition_listener); + + bs_log(BS_WARNING, "Unhandled: reposition on XDG popup %p", xdg_popup_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Handles popup destruction: Removes from parent content, and destroy. */ +void handle_destroy( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_popup_t *xdg_popup_ptr = BS_CONTAINER_OF( + listener_ptr, wlmtk_xdg_popup_t, destroy_listener); + + wlmtk_element_t *element_ptr = wlmtk_content_element( + &xdg_popup_ptr->super_content); + wlmtk_container_remove_element( + element_ptr->parent_container_ptr, + element_ptr); + + wlmtk_xdg_popup_destroy(xdg_popup_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** Handles further popups. Creates them and adds them to parent's content. */ +void handle_new_popup( + struct wl_listener *listener_ptr, + void *data_ptr) +{ + wlmtk_xdg_popup_t *xdg_popup_ptr = BS_CONTAINER_OF( + listener_ptr, wlmtk_xdg_popup_t, new_popup_listener); + struct wlr_xdg_popup *wlr_xdg_popup_ptr = data_ptr; + + wlmtk_xdg_popup_t *new_xdg_popup_ptr = wlmtk_xdg_popup_create( + wlr_xdg_popup_ptr, + wlmtk_content_element(&xdg_popup_ptr->super_content)->env_ptr); + if (NULL == new_xdg_popup_ptr) { + bs_log(BS_ERROR, "Failed wlmtk_xdg_popup_create(%p, %p)", + wlr_xdg_popup_ptr, + wlmtk_content_element(&xdg_popup_ptr->super_content)->env_ptr); + return; + } + + wlmtk_element_set_visible( + wlmtk_content_element(&new_xdg_popup_ptr->super_content), true); + wlmtk_container_add_element( + &xdg_popup_ptr->super_content.super_container, + wlmtk_content_element(&new_xdg_popup_ptr->super_content)); + + bs_log(BS_INFO, "XDG popup %p: New popup %p", + xdg_popup_ptr, new_xdg_popup_ptr); +} + +/* == End of wlmtk_xdg_popup.c ============================================= */ diff --git a/src/wlmtk_xdg_popup.h b/src/wlmtk_xdg_popup.h new file mode 100644 index 00000000..3fe036f6 --- /dev/null +++ b/src/wlmtk_xdg_popup.h @@ -0,0 +1,77 @@ +/* ========================================================================= */ +/** + * @file wlmtk_xdg_popup.h + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __WLMTK_XDG_POPUP_H__ +#define __WLMTK_XDG_POPUP_H__ + +#include "toolkit/toolkit.h" + +#define WLR_USE_UNSTABLE +#include +#undef WLR_USE_UNSTABLE + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** Forward declaration: State of the toolkit's XDG popup. */ +typedef struct _wlmtk_xdg_popup_t wlmtk_xdg_popup_t; + +/** State of toolkit popup. */ +struct _wlmtk_xdg_popup_t { + /** Super class: Content. */ + wlmtk_content_t super_content; + + /** Surface of the popup. */ + wlmtk_surface_t surface; + /** The WLR popup. */ + struct wlr_xdg_popup *wlr_xdg_popup_ptr; + + /** Listener for the `reposition` signal of `wlr_xdg_popup::events` */ + struct wl_listener reposition_listener; + /** Listener for the `destroy` signal of `wlr_xdg_surface::events`. */ + struct wl_listener destroy_listener; + /** Listener for the `new_popup` signal of `wlr_xdg_surface::events`. */ + struct wl_listener new_popup_listener; +}; + +/** + * Creates a popup. + * + * @param wlr_xdg_popup_ptr + * @param env_ptr + */ +wlmtk_xdg_popup_t *wlmtk_xdg_popup_create( + struct wlr_xdg_popup *wlr_xdg_popup_ptr, + wlmtk_env_t *env_ptr); + +/** + * Destroys the popup. + * + * @param xdg_popup_ptr + */ +void wlmtk_xdg_popup_destroy( + wlmtk_xdg_popup_t *xdg_popup_ptr); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* __WLMTK_XDG_POPUP_H__ */ +/* == End of wlmtk_xdg_popup.h ============================================= */ diff --git a/src/wlmtk_xdg_toplevel.c b/src/wlmtk_xdg_toplevel.c new file mode 100644 index 00000000..1cc86dc8 --- /dev/null +++ b/src/wlmtk_xdg_toplevel.c @@ -0,0 +1,789 @@ +/* ========================================================================= */ +/** + * @file xdg_toplevel.c + * + * @copyright + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "xdg_toplevel.h" + +#include "wlmtk_xdg_popup.h" + +/* == Declarations ========================================================= */ + +/** State of the content for an XDG toplevel surface. */ +typedef struct { + /** Super class. */ + wlmtk_surface_t super_surface; + /** The... other super class. FIXME. */ + wlmtk_content_t super_content; + + /** Back-link to server. */ + wlmaker_server_t *server_ptr; + + /** The corresponding wlroots XDG surface. */ + struct wlr_xdg_surface *wlr_xdg_surface_ptr; + /** Whether this surface is currently activated. */ + bool activated; + + /** Listener for the `destroy` signal of the `wlr_xdg_surface::events`. */ + struct wl_listener destroy_listener; + /** Listener for the `new_popup` signal of the `wlr_xdg_surface`. */ + struct wl_listener new_popup_listener; + /** Listener for the `map` signal of the `wlr_surface`. */ + struct wl_listener surface_map_listener; + /** Listener for the `unmap` signal of the `wlr_surface`. */ + struct wl_listener surface_unmap_listener; + /** Listener for the `commit` signal of the `wlr_surface`. */ + struct wl_listener surface_commit_listener; + + /** Listener for `request_maximize` of `wlr_xdg_toplevel::events`. */ + struct wl_listener toplevel_request_maximize_listener; + /** Listener for `request_fullscreen` of `wlr_xdg_toplevel::events`. */ + struct wl_listener toplevel_request_fullscreen_listener; + /** Listener for `request_minimize` of `wlr_xdg_toplevel::events`. */ + struct wl_listener toplevel_request_minimize_listener; + /** Listener for `request_move` signal of `wlr_xdg_toplevel::events`. */ + struct wl_listener toplevel_request_move_listener; + /** Listener for `request_resize` signal of `wlr_xdg_toplevel::events`. */ + struct wl_listener toplevel_request_resize_listener; + /** Listener for `show_window_menu` of `wlr_xdg_toplevel::events`. */ + struct wl_listener toplevel_request_show_window_menu_listener; + /** Listener for `set_parent` of `wlr_xdg_toplevel::events`. */ + struct wl_listener toplevel_set_parent_listener; + /** Listener for `set_title` of the `wlr_xdg_toplevel::events`. */ + struct wl_listener toplevel_set_title_listener; + /** Listener for `set_app_id` of `wlr_xdg_toplevel::events`. */ + struct wl_listener toplevel_set_app_id_listener; +} wlmtk_xdg_toplevel_surface_t; + +static wlmtk_xdg_toplevel_surface_t *xdg_toplevel_surface_create( + struct wlr_xdg_surface *wlr_xdg_surface_ptr, + wlmaker_server_t *server_ptr); +static void xdg_toplevel_surface_destroy( + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr); + +static void handle_destroy( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_new_popup( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_surface_map( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_surface_unmap( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_surface_commit( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_toplevel_request_maximize( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_toplevel_request_fullscreen( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_toplevel_request_minimize( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_toplevel_request_move( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_toplevel_request_resize( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_toplevel_request_show_window_menu( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_toplevel_set_parent( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_toplevel_set_title( + struct wl_listener *listener_ptr, + void *data_ptr); +static void handle_toplevel_set_app_id( + struct wl_listener *listener_ptr, + void *data_ptr); + +static void surface_element_destroy(wlmtk_element_t *element_ptr); +static struct wlr_scene_node *surface_element_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr); +static void surface_request_close( + wlmtk_surface_t *surface_ptr); +static uint32_t surface_request_size( + wlmtk_surface_t *surface_ptr, + int width, + int height); +static void surface_set_activated( + wlmtk_surface_t *surface_ptr, + bool activated); + +static uint32_t content_request_maximized( + wlmtk_content_t *content_ptr, + bool maximized); +static uint32_t content_request_fullscreen( + wlmtk_content_t *content_ptr, + bool fullscreen); + +/* == Data ================================================================= */ + +/** Virtual methods for XDG toplevel surface, for the Element superclass. */ +const wlmtk_element_vmt_t _wlmtk_xdg_toplevel_element_vmt = { + .destroy = surface_element_destroy, + .create_scene_node = surface_element_create_scene_node, +}; + +/** Virtual methods for XDG toplevel surface, for the Surface superclass. */ +const wlmtk_surface_vmt_t _wlmtk_xdg_toplevel_surface_vmt = { + .request_close = surface_request_close, + .request_size = surface_request_size, + .set_activated = surface_set_activated, +}; + +/** Virtual methods for XDG toplevel surface, for the Content superclass. */ +const wlmtk_content_vmt_t _wlmtk_xdg_toplevel_content_vmt = { + .request_maximized = content_request_maximized, + .request_fullscreen = content_request_fullscreen, +}; + +/* == Exported methods ===================================================== */ + +/* ------------------------------------------------------------------------- */ +wlmtk_window_t *wlmtk_window_create_from_xdg_toplevel( + struct wlr_xdg_surface *wlr_xdg_surface_ptr, + wlmaker_server_t *server_ptr) +{ + wlmtk_xdg_toplevel_surface_t *surface_ptr = xdg_toplevel_surface_create( + wlr_xdg_surface_ptr, server_ptr); + if (NULL == surface_ptr) return NULL; + + wlmtk_window_t *wlmtk_window_ptr = wlmtk_window_create( + &surface_ptr->super_content, server_ptr->env_ptr); + if (NULL == wlmtk_window_ptr) { + surface_element_destroy(&surface_ptr->super_surface.super_element); + return NULL; + } + + bs_log(BS_INFO, "Created window %p for wlmtk XDG toplevel surface %p", + wlmtk_window_ptr, surface_ptr); + + return wlmtk_window_ptr; +} + +/* == Local (static) methods =============================================== */ + +/* ------------------------------------------------------------------------- */ +wlmtk_xdg_toplevel_surface_t *xdg_toplevel_surface_create( + struct wlr_xdg_surface *wlr_xdg_surface_ptr, + wlmaker_server_t *server_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = logged_calloc( + 1, sizeof(wlmtk_xdg_toplevel_surface_t)); + if (NULL == xdg_tl_surface_ptr) return NULL; + + if (!wlmtk_surface_init( + &xdg_tl_surface_ptr->super_surface, + wlr_xdg_surface_ptr->surface, + server_ptr->env_ptr)) { + xdg_toplevel_surface_destroy(xdg_tl_surface_ptr); + return NULL; + } + wlmtk_element_extend( + &xdg_tl_surface_ptr->super_surface.super_element, + &_wlmtk_xdg_toplevel_element_vmt); + wlmtk_surface_extend( + &xdg_tl_surface_ptr->super_surface, + &_wlmtk_xdg_toplevel_surface_vmt); + xdg_tl_surface_ptr->wlr_xdg_surface_ptr = wlr_xdg_surface_ptr; + xdg_tl_surface_ptr->server_ptr = server_ptr; + + if (!wlmtk_content_init( + &xdg_tl_surface_ptr->super_content, + &xdg_tl_surface_ptr->super_surface, + server_ptr->env_ptr)) { + xdg_toplevel_surface_destroy(xdg_tl_surface_ptr); + return NULL; + } + wlmtk_content_extend( + &xdg_tl_surface_ptr->super_content, + &_wlmtk_xdg_toplevel_content_vmt); + + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->events.destroy, + &xdg_tl_surface_ptr->destroy_listener, + handle_destroy); + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->events.new_popup, + &xdg_tl_surface_ptr->new_popup_listener, + handle_new_popup); + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->surface->events.map, + &xdg_tl_surface_ptr->surface_map_listener, + handle_surface_map); + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->surface->events.unmap, + &xdg_tl_surface_ptr->surface_unmap_listener, + handle_surface_unmap); + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->surface->events.commit, + &xdg_tl_surface_ptr->surface_commit_listener, + handle_surface_commit); + + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->toplevel->events.request_maximize, + &xdg_tl_surface_ptr->toplevel_request_maximize_listener, + handle_toplevel_request_maximize); + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->toplevel->events.request_fullscreen, + &xdg_tl_surface_ptr->toplevel_request_fullscreen_listener, + handle_toplevel_request_fullscreen); + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->toplevel->events.request_minimize, + &xdg_tl_surface_ptr->toplevel_request_minimize_listener, + handle_toplevel_request_minimize); + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->toplevel->events.request_move, + &xdg_tl_surface_ptr->toplevel_request_move_listener, + handle_toplevel_request_move); + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->toplevel->events.request_resize, + &xdg_tl_surface_ptr->toplevel_request_resize_listener, + handle_toplevel_request_resize); + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->toplevel->events.request_show_window_menu, + &xdg_tl_surface_ptr->toplevel_request_show_window_menu_listener, + handle_toplevel_request_show_window_menu); + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->toplevel->events.set_parent, + &xdg_tl_surface_ptr->toplevel_set_parent_listener, + handle_toplevel_set_parent); + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->toplevel->events.set_title, + &xdg_tl_surface_ptr->toplevel_set_title_listener, + handle_toplevel_set_title); + wlmtk_util_connect_listener_signal( + &wlr_xdg_surface_ptr->toplevel->events.set_app_id, + &xdg_tl_surface_ptr->toplevel_set_app_id_listener, + handle_toplevel_set_app_id); + + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->data = + &xdg_tl_surface_ptr->super_content; + + return xdg_tl_surface_ptr; +} + +/* ------------------------------------------------------------------------- */ +void xdg_toplevel_surface_destroy( + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xts_ptr = xdg_tl_surface_ptr; + wl_list_remove(&xts_ptr->toplevel_set_app_id_listener.link); + wl_list_remove(&xts_ptr->toplevel_set_title_listener.link); + wl_list_remove(&xts_ptr->toplevel_set_parent_listener.link); + wl_list_remove(&xts_ptr->toplevel_request_show_window_menu_listener.link); + wl_list_remove(&xts_ptr->toplevel_request_resize_listener.link); + wl_list_remove(&xts_ptr->toplevel_request_move_listener.link); + wl_list_remove(&xts_ptr->toplevel_request_fullscreen_listener.link); + wl_list_remove(&xts_ptr->toplevel_request_maximize_listener.link); + wl_list_remove(&xts_ptr->toplevel_request_minimize_listener.link); + + wl_list_remove(&xts_ptr->surface_commit_listener.link); + wl_list_remove(&xts_ptr->surface_map_listener.link); + wl_list_remove(&xts_ptr->surface_unmap_listener.link); + wl_list_remove(&xts_ptr->new_popup_listener.link); + wl_list_remove(&xts_ptr->destroy_listener.link); + + wlmtk_content_fini(&xts_ptr->super_content); + + wlmtk_surface_fini(&xts_ptr->super_surface); + free(xts_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Destructor. Wraps to @ref wlmtk_xdg_toplevel_surface_destroy. + * + * @param surface_ptr + */ +void surface_element_destroy(wlmtk_element_t *element_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_xdg_toplevel_surface_t, + super_surface.super_element); + xdg_toplevel_surface_destroy(xdg_tl_surface_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Creates the wlroots scene graph API node, attached to `wlr_scene_tree_ptr`. + * + * @param surface_ptr + * @param wlr_scene_tree_ptr + * + * @return Scene graph API node that represents the surface. + */ +struct wlr_scene_node *surface_element_create_scene_node( + wlmtk_element_t *element_ptr, + struct wlr_scene_tree *wlr_scene_tree_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_xdg_toplevel_surface_t, + super_surface.super_element); + + struct wlr_scene_tree *surface_wlr_scene_tree_ptr = + wlr_scene_xdg_surface_create( + wlr_scene_tree_ptr, + xdg_tl_surface_ptr->wlr_xdg_surface_ptr); + return &surface_wlr_scene_tree_ptr->node; +} + +/* ------------------------------------------------------------------------- */ +/** + * Requests the surface to close: Sends a 'close' message to the toplevel. + * + * @param surface_ptr + */ +void surface_request_close(wlmtk_surface_t *surface_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + surface_ptr, wlmtk_xdg_toplevel_surface_t, super_surface); + + wlr_xdg_toplevel_send_close( + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->toplevel); +} + +/* ------------------------------------------------------------------------- */ +/** + * Sets the dimensions of the element in pixels. + * + * @param surface_ptr + * @param width Width of surface. + * @param height Height of surface. + * + * @return The serial. + */ +uint32_t surface_request_size( + wlmtk_surface_t *surface_ptr, + int width, + int height) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + surface_ptr, wlmtk_xdg_toplevel_surface_t, super_surface); + + return wlr_xdg_toplevel_set_size( + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->toplevel, width, height); +} + +/* ------------------------------------------------------------------------- */ +uint32_t content_request_maximized( + wlmtk_content_t *content_ptr, + bool maximized) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + content_ptr, wlmtk_xdg_toplevel_surface_t, super_content); + + return wlr_xdg_toplevel_set_maximized( + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->toplevel, maximized); +} + +/* ------------------------------------------------------------------------- */ +uint32_t content_request_fullscreen( + wlmtk_content_t *content_ptr, + bool fullscreen) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + content_ptr, wlmtk_xdg_toplevel_surface_t, super_content); + + return wlr_xdg_toplevel_set_fullscreen( + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->toplevel, fullscreen); +} + +/* ------------------------------------------------------------------------- */ +/** + * Sets the keyboard activation status for the surface. + * + * @param surface_ptr + * @param activated + */ +void surface_set_activated( + wlmtk_surface_t *surface_ptr, + bool activated) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + surface_ptr, wlmtk_xdg_toplevel_surface_t, super_surface); + // Early return, if nothing to be done. + if (xdg_tl_surface_ptr->activated == activated) return; + + struct wlr_seat *wlr_seat_ptr = + xdg_tl_surface_ptr->server_ptr->wlr_seat_ptr; + wlr_xdg_toplevel_set_activated( + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->toplevel, activated); + + if (activated) { + struct wlr_keyboard *wlr_keyboard_ptr = wlr_seat_get_keyboard( + wlr_seat_ptr); + if (NULL != wlr_keyboard_ptr) { + wlr_seat_keyboard_notify_enter( + wlr_seat_ptr, + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->surface, + wlr_keyboard_ptr->keycodes, + wlr_keyboard_ptr->num_keycodes, + &wlr_keyboard_ptr->modifiers); + } + } else { + BS_ASSERT(xdg_tl_surface_ptr->activated); + // FIXME: This clears pointer focus. But, this is keyboard focus? + if (wlr_seat_ptr->keyboard_state.focused_surface == + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->surface) { + wlr_seat_pointer_clear_focus(wlr_seat_ptr); + } + } + + xdg_tl_surface_ptr->activated = activated; +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `destroy` signal of the `wlr_xdg_surface::events`. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_destroy(struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, wlmtk_xdg_toplevel_surface_t, destroy_listener); + + bs_log(BS_INFO, "Destroying window %p for wlmtk XDG surface %p", + xdg_tl_surface_ptr, xdg_tl_surface_ptr->super_content.window_ptr); + + wlmtk_window_destroy(xdg_tl_surface_ptr->super_content.window_ptr); + xdg_toplevel_surface_destroy(xdg_tl_surface_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `new_popup` signal. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_new_popup( + struct wl_listener *listener_ptr, + void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, wlmtk_xdg_toplevel_surface_t, new_popup_listener); + struct wlr_xdg_popup *wlr_xdg_popup_ptr = data_ptr; + + wlmtk_xdg_popup_t *xdg_popup_ptr = wlmtk_xdg_popup_create( + wlr_xdg_popup_ptr, xdg_tl_surface_ptr->server_ptr->env_ptr); + if (NULL == xdg_popup_ptr) { + bs_log(BS_ERROR, "Failed wlmtk_xdg_popup_create(%p, %p)", + wlr_xdg_popup_ptr, xdg_tl_surface_ptr->server_ptr->env_ptr); + return; + } + + wlmtk_element_set_visible( + wlmtk_content_element(&xdg_popup_ptr->super_content), true); + wlmtk_container_add_element( + &xdg_tl_surface_ptr->super_content.super_container, + wlmtk_content_element(&xdg_popup_ptr->super_content)); + + bs_log(BS_INFO, "XDG toplevel %p: New popup %p", + xdg_tl_surface_ptr, xdg_popup_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `map` signal. + * + * Issued when the XDG toplevel is fully configured and ready to be shown. + * Will add it to the current workspace. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_surface_map( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, wlmtk_xdg_toplevel_surface_t, surface_map_listener); + + wlmtk_workspace_t *wlmtk_workspace_ptr = wlmaker_workspace_wlmtk( + wlmaker_server_get_current_workspace(xdg_tl_surface_ptr->server_ptr)); + + wlmtk_workspace_map_window( + wlmtk_workspace_ptr, + xdg_tl_surface_ptr->super_content.window_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `unmap` signal. Removes it from the workspace. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_surface_unmap( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, wlmtk_xdg_toplevel_surface_t, surface_unmap_listener); + + wlmtk_window_t *window_ptr = xdg_tl_surface_ptr->super_content.window_ptr; + wlmtk_workspace_unmap_window( + wlmtk_window_get_workspace(window_ptr), + window_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `commit` signal. + * + * @param listener_ptr + * @param data_ptr Points to the const struct wlr_surface. + */ +void handle_surface_commit( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, wlmtk_xdg_toplevel_surface_t, surface_commit_listener); + + if (NULL == xdg_tl_surface_ptr->wlr_xdg_surface_ptr) return; + BS_ASSERT(xdg_tl_surface_ptr->wlr_xdg_surface_ptr->role == + WLR_XDG_SURFACE_ROLE_TOPLEVEL); + + wlmtk_content_commit_size( + &xdg_tl_surface_ptr->super_content, + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->current.configure_serial, + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->current.geometry.width, + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->current.geometry.height); + + wlmtk_window_commit_maximized( + xdg_tl_surface_ptr->super_content.window_ptr, + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->toplevel->current.maximized); + wlmtk_window_commit_fullscreen( + xdg_tl_surface_ptr->super_content.window_ptr, + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->toplevel->current.fullscreen); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `request_maximize` signal. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_toplevel_request_maximize( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, + wlmtk_xdg_toplevel_surface_t, + toplevel_request_maximize_listener); + wlmtk_window_request_maximized( + xdg_tl_surface_ptr->super_content.window_ptr, + !wlmtk_window_is_maximized( + xdg_tl_surface_ptr->super_content.window_ptr)); + + // Protocol expects an `ack_configure`. Depending on current state, that + // may not have been sent throught @ref wlmtk_window_request_maximized, + // hence adding an explicit `ack_configure` here. + wlr_xdg_surface_schedule_configure( + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->toplevel->base); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `request_fullscreen` signal. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_toplevel_request_fullscreen( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, + wlmtk_xdg_toplevel_surface_t, + toplevel_request_maximize_listener); + + wlmtk_window_request_fullscreen( + xdg_tl_surface_ptr->super_content.window_ptr, + !wlmtk_window_is_fullscreen( + xdg_tl_surface_ptr->super_content.window_ptr)); + + // Protocol expects an `ack_configure`. Depending on current state, that + // may not have been sent throught @ref wlmtk_window_request_maximized, + // hence adding an explicit `ack_configure` here. + wlr_xdg_surface_schedule_configure( + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->toplevel->base); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `request_minimize` signal. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_toplevel_request_minimize( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, + wlmtk_xdg_toplevel_surface_t, + toplevel_request_minimize_listener); + + // TODO(kaeser@gubbe.ch): Implement. + bs_log(BS_WARNING, "Unimplemented: request_minimize for XDG toplevel %p", + xdg_tl_surface_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `request_move` signal. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_toplevel_request_move( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, + wlmtk_xdg_toplevel_surface_t, + toplevel_request_move_listener); + wlmtk_window_request_move(xdg_tl_surface_ptr->super_content.window_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `request_resize` signal. + * + * @param listener_ptr + * @param data_ptr Points to a struct wlr_xdg_toplevel_resize_event. + */ +void handle_toplevel_request_resize( + struct wl_listener *listener_ptr, + void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, + wlmtk_xdg_toplevel_surface_t, + toplevel_request_resize_listener); + struct wlr_xdg_toplevel_resize_event *resize_event_ptr = data_ptr; + wlmtk_window_request_resize( + xdg_tl_surface_ptr->super_content.window_ptr, + resize_event_ptr->edges); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `request_show_window_menu` signal. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_toplevel_request_show_window_menu( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, + wlmtk_xdg_toplevel_surface_t, + toplevel_request_show_window_menu_listener); + + // TODO(kaeser@gubbe.ch): Implement. + bs_log(BS_WARNING, "Unimplemented: request_show_window_menu for XDG " + "toplevel %p", xdg_tl_surface_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `set_parent` signal. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_toplevel_set_parent( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, + wlmtk_xdg_toplevel_surface_t, + toplevel_set_parent_listener); + + // TODO(kaeser@gubbe.ch): Implement. + bs_log(BS_WARNING, "Unimplemented: set_parent_menu for XDG toplevel %p", + xdg_tl_surface_ptr); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `set_title` signal. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_toplevel_set_title( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, + wlmtk_xdg_toplevel_surface_t, + toplevel_set_title_listener); + + wlmtk_window_set_title( + xdg_tl_surface_ptr->super_content.window_ptr, + xdg_tl_surface_ptr->wlr_xdg_surface_ptr->toplevel->title); +} + +/* ------------------------------------------------------------------------- */ +/** + * Handler for the `set_app_id` signal. + * + * @param listener_ptr + * @param data_ptr + */ +void handle_toplevel_set_app_id( + struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + wlmtk_xdg_toplevel_surface_t *xdg_tl_surface_ptr = BS_CONTAINER_OF( + listener_ptr, + wlmtk_xdg_toplevel_surface_t, + toplevel_set_app_id_listener); + + // TODO(kaeser@gubbe.ch): Implement. + bs_log(BS_WARNING, "Unimplemented: set_parent_menu for XDG toplevel %p", + xdg_tl_surface_ptr); +} + +/* == End of xdg_toplevel.c ================================================ */ diff --git a/src/util.c b/src/wlmtk_xdg_toplevel.h similarity index 52% rename from src/util.c rename to src/wlmtk_xdg_toplevel.h index 40683822..c4cae753 100644 --- a/src/util.c +++ b/src/wlmtk_xdg_toplevel.h @@ -1,6 +1,6 @@ /* ========================================================================= */ /** - * @file util.c + * @file xdg_toplevel.h * * @copyright * Copyright 2023 Google LLC @@ -17,19 +17,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#ifndef __WLMTK_XDG_TOPLEVEL_H__ +#define __WLMTK_XDG_TOPLEVEL_H__ -#include "util.h" +#include "server.h" +#include "toolkit/toolkit.h" -/* == Exported methods ===================================================== */ +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus -/* ------------------------------------------------------------------------- */ -void wlm_util_connect_listener_signal( - struct wl_signal *signal_ptr, - struct wl_listener *listener_ptr, - void (*notifier_func)(struct wl_listener *, void *)) -{ - listener_ptr->notify = notifier_func; - wl_signal_add(signal_ptr, listener_ptr); -} +/** + * Creates a toolkit window with the XDG surface as content. + * + * @param wlr_xdg_surface_ptr + * + * @return The window, or NULL on error. + */ +wlmtk_window_t *wlmtk_window_create_from_xdg_toplevel( + struct wlr_xdg_surface *wlr_xdg_surface_ptr, + wlmaker_server_t *server_ptr); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus -/* == End of util.c ======================================================== */ +#endif /* __WLMTK_XDG_TOPLEVEL_H__ */ +/* == End of xdg_toplevel.h ================================================ */ diff --git a/src/workspace.c b/src/workspace.c index b55f47b7..7238964e 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -21,6 +21,7 @@ #include "workspace.h" #include "tile_container.h" +#include "toolkit/toolkit.h" #include @@ -70,6 +71,9 @@ struct _wlmaker_workspace_t { /** Scene graph subtree holding all layers of this workspace. */ struct wlr_scene_tree *wlr_scene_tree_ptr; + /** Transitional: Link up to toolkit workspace. */ + wlmtk_workspace_t *wlmtk_workspace_ptr; + /** Data regarding each layer. */ wlmaker_workspace_layer_data_t layers[WLMAKER_WORKSPACE_LAYER_NUM]; @@ -165,6 +169,18 @@ wlmaker_workspace_t *wlmaker_workspace_create(wlmaker_server_t *server_ptr, workspace_ptr->injectable_view_set_active = wlmaker_view_set_active; wlmaker_workspace_arrange_views(workspace_ptr); + + workspace_ptr->wlmtk_workspace_ptr = wlmtk_workspace_create( + workspace_ptr->server_ptr->env_ptr, workspace_ptr->wlr_scene_tree_ptr); + if (NULL == workspace_ptr->wlmtk_workspace_ptr) { + wlmaker_workspace_destroy(workspace_ptr); + return NULL; + } + struct wlr_box extents; + wlr_output_layout_get_box( + workspace_ptr->server_ptr->wlr_output_layout_ptr, NULL, &extents); + wlmtk_workspace_set_extents(workspace_ptr->wlmtk_workspace_ptr, &extents); + return workspace_ptr; } @@ -203,6 +219,11 @@ void wlmaker_workspace_destroy(wlmaker_workspace_t *workspace_ptr) workspace_ptr->fullscreen_wlr_scene_tree_ptr = NULL; } + if (NULL != workspace_ptr->wlmtk_workspace_ptr) { + wlmtk_workspace_destroy(workspace_ptr->wlmtk_workspace_ptr); + workspace_ptr->wlmtk_workspace_ptr = NULL; + } + if (NULL != workspace_ptr->wlr_scene_tree_ptr) { wlr_scene_node_destroy(&workspace_ptr->wlr_scene_tree_ptr->node); } @@ -409,6 +430,15 @@ const bs_dllist_t *wlmaker_workspace_get_views_dllist( return &workspace_ptr->views; } +/* ------------------------------------------------------------------------- */ +void wlmaker_workspace_set_extents( + wlmaker_workspace_t *workspace_ptr, + const struct wlr_box *extents_ptr) +{ + wlmtk_workspace_set_extents(workspace_ptr->wlmtk_workspace_ptr, + extents_ptr); +} + /* ------------------------------------------------------------------------- */ void wlmaker_workspace_arrange_views(wlmaker_workspace_t *workspace_ptr) { @@ -639,6 +669,12 @@ wlmaker_tile_container_t *wlmaker_workspace_get_tile_container( return workspace_ptr->tile_container_ptr; } +/* ------------------------------------------------------------------------- */ +wlmtk_workspace_t *wlmaker_workspace_wlmtk(wlmaker_workspace_t *workspace_ptr) +{ + return workspace_ptr->wlmtk_workspace_ptr; +} + /* == Static (local) methods =============================================== */ /* ------------------------------------------------------------------------- */ diff --git a/src/workspace.h b/src/workspace.h index 32d39a2a..783d1243 100644 --- a/src/workspace.h +++ b/src/workspace.h @@ -50,6 +50,7 @@ enum _wlmaker_workspace_layer_t { #include "layer_surface.h" #include "server.h" #include "tile_container.h" +#include "toolkit/toolkit.h" #ifdef __cplusplus extern "C" { @@ -188,6 +189,18 @@ void wlmaker_workspace_activate_previous_view( const bs_dllist_t *wlmaker_workspace_get_views_dllist( wlmaker_workspace_t *workspace_ptr); +/** + * Sets extents of the workspace. + * + * TODO(kaeser@gubbe.ch): Should re-trigger re-arranging. + * + * @param workspace_ptr + * @param extents_ptr + */ +void wlmaker_workspace_set_extents( + wlmaker_workspace_t *workspace_ptr, + const struct wlr_box *extents_ptr); + /** * (Re)arranges the views in the workspace. * @@ -333,6 +346,9 @@ bs_dllist_node_t *wlmaker_dlnode_from_workspace( wlmaker_tile_container_t *wlmaker_workspace_get_tile_container( wlmaker_workspace_t *workspace_ptr); +/** Transitional: Returns the @ref wlmtk_workspace_t. */ +wlmtk_workspace_t *wlmaker_workspace_wlmtk(wlmaker_workspace_t *workspace_ptr); + /** Unit tests. */ extern const bs_test_case_t wlmaker_workspace_test_cases[]; diff --git a/src/xdg_decoration.c b/src/xdg_decoration.c index 48709186..c3134a1c 100644 --- a/src/xdg_decoration.c +++ b/src/xdg_decoration.c @@ -23,7 +23,7 @@ #include #include "config.h" -#include "util.h" +#include "toolkit/toolkit.h" #define WLR_USE_UNSTABLE #include @@ -92,12 +92,12 @@ wlmaker_xdg_decoration_manager_t *wlmaker_xdg_decoration_manager_create( return NULL; } - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &decoration_manager_ptr->wlr_xdg_decoration_manager_v1_ptr-> events.new_toplevel_decoration, &decoration_manager_ptr->new_toplevel_decoration_listener, handle_new_toplevel_decoration); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &decoration_manager_ptr->wlr_xdg_decoration_manager_v1_ptr-> events.destroy, &decoration_manager_ptr->destroy_listener, @@ -179,11 +179,11 @@ wlmaker_xdg_decoration_t *wlmaker_xdg_decoration_create( decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr = wlr_xdg_toplevel_decoration_v1_ptr; - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->events.destroy, &decoration_ptr->destroy_listener, handle_decoration_destroy); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->events.request_mode, &decoration_ptr->request_mode_listener, handle_decoration_request_mode); @@ -217,9 +217,9 @@ void handle_decoration_request_mode( { wlmaker_xdg_decoration_t *decoration_ptr = wl_container_of( listener_ptr, decoration_ptr, request_mode_listener); - struct wlr_scene_tree *wlr_scene_tree_ptr = (struct wlr_scene_tree*) - decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->surface->data; - wlmaker_view_t *view_ptr = (wlmaker_view_t*)wlr_scene_tree_ptr->node.data; + + wlmtk_content_t *content_ptr = (wlmtk_content_t*) + decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->toplevel->base->data; enum wlr_xdg_toplevel_decoration_v1_mode mode = decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->requested_mode; @@ -253,19 +253,24 @@ void handle_decoration_request_mode( wlr_xdg_toplevel_decoration_v1_set_mode( decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr, mode); - bs_log(BS_INFO, "XDG decoration request_mode for XDG surface %p, view %p: " - "Current %d, pending %d, scheduled %d, requested %d. Set: %d", - decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->surface, - view_ptr, - decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->current.mode, - decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->pending.mode, - decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->scheduled_mode, - decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->requested_mode, - mode); - - wlmaker_view_set_server_side_decoration( - view_ptr, - mode != WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); + if (NULL != content_ptr && + content_ptr->identifier_ptr == wlmtk_content_identifier_ptr) { + + bs_log(BS_INFO, "XDG decoration request_mode for XDG surface %p, " + "content %p: Current %d, pending %d, scheduled %d, " + "requested %d. Set: %d", + decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->toplevel->base->surface, + content_ptr, + decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->current.mode, + decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->pending.mode, + decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->scheduled_mode, + decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->requested_mode, + mode); + + wlmtk_window_set_server_side_decorated( + content_ptr->window_ptr, + mode != WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); + } } /* ------------------------------------------------------------------------- */ diff --git a/src/xdg_popup.c b/src/xdg_popup.c index 90528af6..ec67a42a 100644 --- a/src/xdg_popup.c +++ b/src/xdg_popup.c @@ -22,7 +22,7 @@ #include -#include "util.h" +#include "toolkit/toolkit.h" /* == Declarations ========================================================= */ @@ -58,11 +58,11 @@ wlmaker_xdg_popup_t *wlmaker_xdg_popup_create( if (NULL == xdg_popup_ptr) return NULL; xdg_popup_ptr->wlr_xdg_popup_ptr = wlr_xdg_popup_ptr; - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_popup_ptr->base->events.destroy, &xdg_popup_ptr->destroy_listener, handle_destroy); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_popup_ptr->base->events.new_popup, &xdg_popup_ptr->new_popup_listener, handle_new_popup); diff --git a/src/xdg_shell.c b/src/xdg_shell.c index 4c10e674..ed658e67 100644 --- a/src/xdg_shell.c +++ b/src/xdg_shell.c @@ -20,11 +20,13 @@ #include "xdg_shell.h" -#include "util.h" +#include "toolkit/toolkit.h" #include "view.h" +#include "wlmtk_xdg_toplevel.h" #include "xdg_toplevel.h" #include +#include /* == Declarations ========================================================= */ @@ -52,11 +54,11 @@ wlmaker_xdg_shell_t *wlmaker_xdg_shell_create(wlmaker_server_t *server_ptr) return NULL; } - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &xdg_shell_ptr->wlr_xdg_shell_ptr->events.new_surface, &xdg_shell_ptr->new_surface_listener, handle_new_surface); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &xdg_shell_ptr->wlr_xdg_shell_ptr->events.destroy, &xdg_shell_ptr->destroy_listener, handle_destroy); @@ -101,9 +103,8 @@ void handle_new_surface(struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlr_xdg_surface *wlr_xdg_surface_ptr; - wlmaker_xdg_toplevel_t *xdg_toplevel_ptr; - wlmaker_xdg_shell_t *xdg_shell_ptr = wl_container_of( - listener_ptr, xdg_shell_ptr, new_surface_listener); + wlmaker_xdg_shell_t *xdg_shell_ptr = BS_CONTAINER_OF( + listener_ptr, wlmaker_xdg_shell_t, new_surface_listener); wlr_xdg_surface_ptr = data_ptr; switch (wlr_xdg_surface_ptr->role) { @@ -115,10 +116,11 @@ void handle_new_surface(struct wl_listener *listener_ptr, break; case WLR_XDG_SURFACE_ROLE_TOPLEVEL: - xdg_toplevel_ptr = wlmaker_xdg_toplevel_create( - xdg_shell_ptr, wlr_xdg_surface_ptr); - bs_log(BS_INFO, "XDG shell: Surface %p created toplevel view %p", - wlr_xdg_surface_ptr, xdg_toplevel_ptr); + + wlmtk_window_t *window_ptr = wlmtk_window_create_from_xdg_toplevel( + wlr_xdg_surface_ptr, xdg_shell_ptr->server_ptr); + bs_log(BS_INFO, "XDG shell: Toolkit window %p for surface %p", + window_ptr, wlr_xdg_surface_ptr); break; default: diff --git a/src/xdg_toplevel.c b/src/xdg_toplevel.c index ef9238a9..36acd050 100644 --- a/src/xdg_toplevel.c +++ b/src/xdg_toplevel.c @@ -21,9 +21,11 @@ #include "xdg_toplevel.h" #include "iconified.h" -#include "util.h" +#include "toolkit/toolkit.h" #include "xdg_popup.h" +#include "toolkit/toolkit.h" + /* == Declarations ========================================================= */ /** State of an XDG toplevel surface. */ @@ -45,7 +47,6 @@ struct _wlmaker_xdg_toplevel_t { /** ID of the last 'set_size' call. */ uint32_t pending_resize_serial; - /** Listener for the `destroy` signal of the `wlr_xdg_surface`. */ struct wl_listener destroy_listener; /** Listener for the `new_popup` signal of the `wlr_xdg_surface`. */ @@ -109,12 +110,6 @@ static void handle_toplevel_fullscreen( static void handle_toplevel_minimize( struct wl_listener *listener_ptr, void *data_ptr); -static void handle_toplevel_move( - struct wl_listener *listener_ptr, - void *data_ptr); -static void handle_toplevel_resize( - struct wl_listener *listener_ptr, - void *data_ptr); static void handle_toplevel_show_window_menu( struct wl_listener *listener_ptr, void *data_ptr); @@ -147,6 +142,7 @@ static void wlmaker_xdg_toplevel_set_fullscreen( wlmaker_view_t *view_ptr, bool fullscreen); + /* == Data ================================================================= */ /** View implementor methods. */ @@ -170,61 +166,53 @@ wlmaker_xdg_toplevel_t *wlmaker_xdg_toplevel_create( if (NULL == xdg_toplevel_ptr) return NULL; xdg_toplevel_ptr->wlr_xdg_surface_ptr = wlr_xdg_surface_ptr; - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_surface_ptr->events.destroy, &xdg_toplevel_ptr->destroy_listener, handle_destroy); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_surface_ptr->events.new_popup, &xdg_toplevel_ptr->new_popup_listener, handle_new_popup); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_surface_ptr->surface->events.map, &xdg_toplevel_ptr->surface_map_listener, handle_map); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_surface_ptr->surface->events.unmap, &xdg_toplevel_ptr->surface_unmap_listener, handle_unmap); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_surface_ptr->surface->events.commit, &xdg_toplevel_ptr->surface_commit_listener, handle_surface_commit); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_surface_ptr->toplevel->events.request_maximize, &xdg_toplevel_ptr->toplevel_request_maximize_listener, handle_toplevel_maximize); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_surface_ptr->toplevel->events.request_fullscreen, &xdg_toplevel_ptr->toplevel_request_fullscreen_listener, handle_toplevel_fullscreen); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_surface_ptr->toplevel->events.request_minimize, &xdg_toplevel_ptr->toplevel_request_minimize_listener, handle_toplevel_minimize); - wlm_util_connect_listener_signal( - &wlr_xdg_surface_ptr->toplevel->events.request_move, - &xdg_toplevel_ptr->toplevel_request_move_listener, - handle_toplevel_move); - wlm_util_connect_listener_signal( - &wlr_xdg_surface_ptr->toplevel->events.request_resize, - &xdg_toplevel_ptr->toplevel_request_resize_listener, - handle_toplevel_resize); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_surface_ptr->toplevel->events.request_show_window_menu, &xdg_toplevel_ptr->toplevel_request_show_window_menu_listener, handle_toplevel_show_window_menu); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_surface_ptr->toplevel->events.set_parent, &xdg_toplevel_ptr->toplevel_set_parent_listener, handle_toplevel_set_parent); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_surface_ptr->toplevel->events.set_title, &xdg_toplevel_ptr->toplevel_set_title_listener, handle_toplevel_set_title); - wlm_util_connect_listener_signal( + wlmtk_util_connect_listener_signal( &wlr_xdg_surface_ptr->toplevel->events.set_app_id, &xdg_toplevel_ptr->toplevel_set_app_id_listener, handle_toplevel_set_app_id); @@ -250,7 +238,6 @@ wlmaker_xdg_toplevel_t *wlmaker_xdg_toplevel_create( xdg_toplevel_ptr->wlr_scene_tree_ptr; wlmaker_view_set_position(&xdg_toplevel_ptr->view, 32, 40); - return xdg_toplevel_ptr; } @@ -574,47 +561,6 @@ void handle_toplevel_minimize( wlmaker_view_set_iconified(&xdg_toplevel_ptr->view, true); } -/* ------------------------------------------------------------------------- */ -/** - * Handler for the `move` signal of the `struct wlr_xdg_toplevel`. - * - * @param listener_ptr - * @param data_ptr - */ -void handle_toplevel_move( - struct wl_listener *listener_ptr, - __UNUSED__ void *data_ptr) -{ - wlmaker_xdg_toplevel_t *xdg_toplevel_ptr = wl_container_of( - listener_ptr, xdg_toplevel_ptr, toplevel_request_move_listener); - - wlmaker_cursor_begin_move( - xdg_toplevel_ptr->view.server_ptr->cursor_ptr, - &xdg_toplevel_ptr->view); -} - -/* ------------------------------------------------------------------------- */ -/** - * Handler for the `resize` signal of the `struct wlr_xdg_toplevel`. - * - * @param listener_ptr - * @param data_ptr - */ -void handle_toplevel_resize( - struct wl_listener *listener_ptr, - void *data_ptr) -{ - wlmaker_xdg_toplevel_t *xdg_toplevel_ptr = wl_container_of( - listener_ptr, xdg_toplevel_ptr, toplevel_request_resize_listener); - struct wlr_xdg_toplevel_resize_event *wlr_xdg_toplevel_resize_event_ptr = - data_ptr; - - wlmaker_cursor_begin_resize( - xdg_toplevel_ptr->view.server_ptr->cursor_ptr, - &xdg_toplevel_ptr->view, - wlr_xdg_toplevel_resize_event_ptr->edges); -} - /* ------------------------------------------------------------------------- */ /** * Handler for the `show_window_menu` signal of the `struct wlr_xdg_toplevel`. diff --git a/src/xdg_toplevel.h b/src/xdg_toplevel.h index 5b531d05..98bce69d 100644 --- a/src/xdg_toplevel.h +++ b/src/xdg_toplevel.h @@ -21,6 +21,7 @@ #define __XDG_TOPLEVEL_H__ #include "xdg_shell.h" +#include "toolkit/toolkit.h" #ifdef __cplusplus extern "C" { diff --git a/submodules/libbase b/submodules/libbase index cde45d59..c215f7db 160000 --- a/submodules/libbase +++ b/submodules/libbase @@ -1 +1 @@ -Subproject commit cde45d596a2349eb90c7023dcda79b2b2b1e03d1 +Subproject commit c215f7dbedc624ff29808882c2e0015d4a584b77 diff --git a/testdata/toolkit/resizebar_area_pressed.png b/testdata/toolkit/resizebar_area_pressed.png new file mode 100644 index 00000000..4257ee74 Binary files /dev/null and b/testdata/toolkit/resizebar_area_pressed.png differ diff --git a/testdata/toolkit/resizebar_area_released.png b/testdata/toolkit/resizebar_area_released.png new file mode 100644 index 00000000..9724ecca Binary files /dev/null and b/testdata/toolkit/resizebar_area_released.png differ diff --git a/testdata/toolkit/title_blurred.png b/testdata/toolkit/title_blurred.png new file mode 100644 index 00000000..52c3009a Binary files /dev/null and b/testdata/toolkit/title_blurred.png differ diff --git a/testdata/toolkit/title_blurred_short.png b/testdata/toolkit/title_blurred_short.png new file mode 100644 index 00000000..24017c22 Binary files /dev/null and b/testdata/toolkit/title_blurred_short.png differ diff --git a/testdata/toolkit/title_button_blurred.png b/testdata/toolkit/title_button_blurred.png new file mode 100644 index 00000000..8376b597 Binary files /dev/null and b/testdata/toolkit/title_button_blurred.png differ diff --git a/testdata/toolkit/title_button_focussed_pressed.png b/testdata/toolkit/title_button_focussed_pressed.png new file mode 100644 index 00000000..caa63909 Binary files /dev/null and b/testdata/toolkit/title_button_focussed_pressed.png differ diff --git a/testdata/toolkit/title_button_focussed_released.png b/testdata/toolkit/title_button_focussed_released.png new file mode 100644 index 00000000..3554ccef Binary files /dev/null and b/testdata/toolkit/title_button_focussed_released.png differ diff --git a/testdata/toolkit/title_focussed.png b/testdata/toolkit/title_focussed.png new file mode 100644 index 00000000..72778679 Binary files /dev/null and b/testdata/toolkit/title_focussed.png differ