diff --git a/apps/linux_uconsole_gtk/src/platform/gtk/gtk_canvas_presenter.cpp b/apps/linux_uconsole_gtk/src/platform/gtk/gtk_canvas_presenter.cpp index 6d293e36..42da0e96 100644 --- a/apps/linux_uconsole_gtk/src/platform/gtk/gtk_canvas_presenter.cpp +++ b/apps/linux_uconsole_gtk/src/platform/gtk/gtk_canvas_presenter.cpp @@ -20,7 +20,10 @@ struct GtkCanvasPresenter::Impl GtkWindow* window = nullptr; GtkDrawingArea* drawing_area = nullptr; GtkEventController* key_controller = nullptr; + GtkEventController* motion_controller = nullptr; + GtkGesture* click_gesture = nullptr; bool running = true; + bool fullscreen = false; bool startup_inputs_queued = false; bool startup_shortcut_queued = false; PointerState pointer{}; @@ -41,13 +44,86 @@ void queueSpecial(std::vector& input, input.push_back(InputEvent{key, label, '\0'}); } +void updatePointer(GtkCanvasPresenter::Impl& impl, + double widget_x, + double widget_y, + bool pressed) +{ + const int widget_width = + gtk_widget_get_width(GTK_WIDGET(impl.drawing_area)); + const int widget_height = + gtk_widget_get_height(GTK_WIDGET(impl.drawing_area)); + if (widget_width <= 0 || widget_height <= 0 || + impl.frame_width <= 0 || impl.frame_height <= 0) + { + return; + } + const double scale_x = + static_cast(widget_width) / impl.frame_width; + const double scale_y = + static_cast(widget_height) / impl.frame_height; + const double scale = std::min(scale_x, scale_y); + const double draw_width = impl.frame_width * scale; + const double draw_height = impl.frame_height * scale; + const double offset_x = (widget_width - draw_width) / 2.0; + const double offset_y = (widget_height - draw_height) / 2.0; + impl.pointer.x = std::clamp( + static_cast((widget_x - offset_x) / scale), 0, + std::max(0, impl.frame_width - 1)); + impl.pointer.y = std::clamp( + static_cast((widget_y - offset_y) / scale), 0, + std::max(0, impl.frame_height - 1)); + impl.pointer.pressed = pressed; +} + +void onMotion(GtkEventControllerMotion*, double x, double y, gpointer data) +{ + auto* impl = static_cast(data); + updatePointer(*impl, x, y, impl->pointer.pressed); +} + +void onPressed(GtkGestureClick*, int, double x, double y, gpointer data) +{ + auto* impl = static_cast(data); + updatePointer(*impl, x, y, true); +} + +void onReleased(GtkGestureClick*, int, double x, double y, gpointer data) +{ + auto* impl = static_cast(data); + updatePointer(*impl, x, y, false); +} + gboolean onKeyPressed(GtkEventControllerKey*, guint keyval, guint, - GdkModifierType, + GdkModifierType modifiers, gpointer data) { auto* impl = static_cast(data); + if ((modifiers & GDK_CONTROL_MASK) != 0 && + (keyval == GDK_KEY_m || keyval == GDK_KEY_M)) + { + gtk_window_minimize(impl->window); + return TRUE; + } + if (((modifiers & GDK_CONTROL_MASK) != 0 && + (keyval == GDK_KEY_q || keyval == GDK_KEY_Q)) || + ((modifiers & GDK_ALT_MASK) != 0 && keyval == GDK_KEY_F4)) + { + impl->running = false; + gtk_window_destroy(impl->window); + return TRUE; + } + if (keyval == GDK_KEY_F11) + { + impl->fullscreen = !impl->fullscreen; + if (impl->fullscreen) + gtk_window_fullscreen(impl->window); + else + gtk_window_unfullscreen(impl->window); + return TRUE; + } switch (keyval) { case GDK_KEY_BackSpace: @@ -162,6 +238,7 @@ GtkCanvasPresenter::GtkCanvasPresenter(GtkCanvasPresenterOptions options) impl_->options.height = std::max(240, impl_->options.height); gtk_init(); + impl_->fullscreen = impl_->options.fullscreen; impl_->window = GTK_WINDOW(gtk_window_new()); gtk_window_set_title(impl_->window, impl_->options.title.c_str()); gtk_window_set_default_size(impl_->window, impl_->options.width, @@ -180,6 +257,18 @@ GtkCanvasPresenter::GtkCanvasPresenter(GtkCanvasPresenterOptions options) G_CALLBACK(onKeyPressed), impl_.get()); gtk_widget_add_controller(GTK_WIDGET(impl_->drawing_area), impl_->key_controller); + impl_->motion_controller = gtk_event_controller_motion_new(); + g_signal_connect(impl_->motion_controller, "motion", G_CALLBACK(onMotion), + impl_.get()); + gtk_widget_add_controller(GTK_WIDGET(impl_->drawing_area), + impl_->motion_controller); + impl_->click_gesture = gtk_gesture_click_new(); + g_signal_connect(impl_->click_gesture, "pressed", G_CALLBACK(onPressed), + impl_.get()); + g_signal_connect(impl_->click_gesture, "released", + G_CALLBACK(onReleased), impl_.get()); + gtk_widget_add_controller(GTK_WIDGET(impl_->drawing_area), + GTK_EVENT_CONTROLLER(impl_->click_gesture)); g_signal_connect(impl_->window, "close-request", G_CALLBACK(onCloseRequest), impl_.get()); gtk_window_present(impl_->window); @@ -237,7 +326,7 @@ std::vector GtkCanvasPresenter::drainInput() bool GtkCanvasPresenter::supportsPointer() const noexcept { - return false; + return true; } GtkCanvasPresenter::PointerState GtkCanvasPresenter::pointerState() const diff --git a/platform/linux/uconsole/src/uconsole_desktop_shell.cpp b/platform/linux/uconsole/src/uconsole_desktop_shell.cpp index 8d76bf5c..3d73f5e1 100644 --- a/platform/linux/uconsole/src/uconsole_desktop_shell.cpp +++ b/platform/linux/uconsole/src/uconsole_desktop_shell.cpp @@ -908,13 +908,13 @@ class UConsoleDesktopShell switch (active_section_) { case Section::Chat: - return "\\ Nav | Tab Focus | Enter Open/Send | F1 Help"; + return "\\ Nav | Tab Focus | Enter | F11 Full | ^M Min | ^Q Quit"; case Section::Map: - return "\\ Nav | M Map | [ ] Workspace | F1 Help"; + return "\\ Nav | M Map | [ ] Page | F11 Full | ^M Min | ^Q Quit"; case Section::Overview: - return "\\ Nav | C Chat | M Map | [ ] Page | F1 Help"; + return "\\ Nav | C Chat | M Map | [ ] Page | F11 Full | ^Q Quit"; default: - return "\\ Nav | Enter Open | [ ] Workspace | F1 Help"; + return "\\ Nav | Enter | [ ] Page | F11 Full | ^M Min | ^Q Quit"; } } @@ -1423,7 +1423,7 @@ class UConsoleDesktopShell for (int column = 0; column < 3; ++column) { const int tile_index = (row_index * 3) + column; - lv_obj_t* tile = lv_obj_create(row); + lv_obj_t* tile = lv_image_create(row); resetBox(tile); lv_obj_set_width(tile, 0); lv_obj_set_height(tile, LV_PCT(100)); @@ -1445,6 +1445,24 @@ class UConsoleDesktopShell ? tile_colors[static_cast(tile_index)] : embedded_palette::kSurfaceAlt), 0); + if (available) + { + map_tile_paths_[static_cast(tile_index)] = + "A:" + snapshot + .tiles[static_cast(tile_index)] + .path.string(); + lv_image_set_inner_align(tile, LV_IMAGE_ALIGN_STRETCH); + lv_image_set_src( + tile, + map_tile_paths_[static_cast(tile_index)] + .c_str()); + } + else + { + map_tile_paths_[static_cast(tile_index)] + .clear(); + lv_image_set_src(tile, nullptr); + } } } map_meta_label_ = createLabel( @@ -1832,6 +1850,28 @@ class UConsoleDesktopShell setLabel(map_cache_label_, mapCacheSummary(snapshot)); setLabel(map_download_label_, mapDownloadSummary(snapshot)); setLabel(map_retry_label_, mapNodeSummary(snapshot)); + for (std::size_t index = 0; + index < map_tile_cells_.size() && index < snapshot.tiles.size(); + ++index) + { + auto* tile = map_tile_cells_[index]; + if (tile == nullptr) continue; + if (snapshot.tiles[index].available) + { + map_tile_paths_[index] = + "A:" + snapshot.tiles[index].path.string(); + lv_image_set_inner_align(tile, LV_IMAGE_ALIGN_STRETCH); + lv_image_set_src(tile, map_tile_paths_[index].c_str()); + lv_obj_set_style_bg_color( + tile, color(embedded_palette::kMapTile2), 0); + } + else + { + lv_image_set_src(tile, nullptr); + lv_obj_set_style_bg_color( + tile, color(embedded_palette::kSurfaceAlt), 0); + } + } if (map_download_jobs_.empty()) { requestMissingMapTiles(snapshot); @@ -2488,6 +2528,7 @@ class UConsoleDesktopShell lv_obj_t* map_download_label_ = nullptr; lv_obj_t* map_retry_label_ = nullptr; std::array map_tile_cells_{}; + std::array map_tile_paths_{}; std::vector> map_download_jobs_{};