diff --git a/Core/plottable/Performance_Overlay.cpp b/Core/plottable/Performance_Overlay.cpp index 9a6e890..8a632c3 100644 --- a/Core/plottable/Performance_Overlay.cpp +++ b/Core/plottable/Performance_Overlay.cpp @@ -322,8 +322,13 @@ bool performance_overlay_handle_pointer_release(Plot_Core& plot, PointF position auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); return shower && shower->enabled() && shower->handle_pointer_release(position); } +bool performance_overlay_handle_pointer_leave(Plot_Core& plot) { + auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); + return shower && shower->enabled() && shower->handle_pointer_leave(); +} void Performance_Overlay::set_enabled(bool enabled) { auto worker = d()->metrics_worker; + d()->reset_button_states(); d()->enabled_value.store(enabled, std::memory_order_release); worker->enabled.store(enabled, std::memory_order_release); if (!enabled) { @@ -434,14 +439,11 @@ bool Performance_Overlay::handle_pointer_press(PointF position) { auto snapshot = display_snapshot(); if (!snapshot) return false; - if (!snapshot->copy_button_rect.empty() && snapshot->copy_button_rect.contains(position)) { - d()->copy_to_clipboard(performance_snapshot_text(*snapshot)); + Button_Event_Result copy_result = d()->copy_button_interaction.pointer_press(position, snapshot->copy_button_rect); + Button_Event_Result close_result = d()->close_button_interaction.pointer_press(position, snapshot->close_button_rect); + d()->publish_button_states(copy_result.changed || close_result.changed); + if (copy_result.consumed || close_result.consumed) return true; - } - if (!snapshot->close_button_rect.empty() && snapshot->close_button_rect.contains(position)) { - set_enabled(false); - return true; - } if (snapshot->max_scroll_offset <= 0.0) return false; if (snapshot->scroll_thumb_rect.contains(position)) { @@ -461,13 +463,32 @@ bool Performance_Overlay::handle_pointer_press(PointF position) { return false; } bool Performance_Overlay::handle_pointer_move(PointF position) { - if (!d()->metrics_worker->drag_active.load(std::memory_order_acquire)) + if (d()->metrics_worker->drag_active.load(std::memory_order_acquire)) { + d()->metrics_worker->drag_position_y.store(position.y, std::memory_order_release); + schedule_performance_metrics_worker(d()->metrics_worker); + return true; + } + auto snapshot = display_snapshot(); + if (!snapshot) return false; - d()->metrics_worker->drag_position_y.store(position.y, std::memory_order_release); - schedule_performance_metrics_worker(d()->metrics_worker); - return true; + Button_Event_Result copy_result = d()->copy_button_interaction.pointer_move(position, snapshot->copy_button_rect); + Button_Event_Result close_result = d()->close_button_interaction.pointer_move(position, snapshot->close_button_rect); + d()->publish_button_states(copy_result.changed || close_result.changed); + return copy_result.consumed || close_result.consumed; } bool Performance_Overlay::handle_pointer_release(PointF position) { + auto snapshot = display_snapshot(); + if (snapshot) { + Button_Event_Result copy_result = d()->copy_button_interaction.pointer_release(position, snapshot->copy_button_rect); + Button_Event_Result close_result = d()->close_button_interaction.pointer_release(position, snapshot->close_button_rect); + d()->publish_button_states(copy_result.changed || close_result.changed); + if (copy_result.clicked) + d()->copy_to_clipboard(performance_snapshot_text(*snapshot)); + if (close_result.clicked) + set_enabled(false); + if (copy_result.consumed || close_result.consumed) + return true; + } if (!d()->metrics_worker->drag_active.load(std::memory_order_acquire)) return false; d()->metrics_worker->drag_position_y.store(position.y, std::memory_order_release); @@ -475,6 +496,12 @@ bool Performance_Overlay::handle_pointer_release(PointF position) { schedule_performance_metrics_worker(d()->metrics_worker); return true; } +bool Performance_Overlay::handle_pointer_leave() { + Button_Event_Result copy_result = d()->copy_button_interaction.pointer_leave(); + Button_Event_Result close_result = d()->close_button_interaction.pointer_leave(); + d()->publish_button_states(copy_result.changed || close_result.changed); + return copy_result.consumed || close_result.consumed; +} void Performance_Overlay::set_update_callback(std::function callback) { std::lock_guard lock(d()->metrics_worker->update_callback_mutex); d()->metrics_worker->update_callback = std::move(callback); diff --git a/Core/plottable/Performance_Overlay.h b/Core/plottable/Performance_Overlay.h index 5de4eb0..af35c04 100644 --- a/Core/plottable/Performance_Overlay.h +++ b/Core/plottable/Performance_Overlay.h @@ -63,6 +63,7 @@ public: bool handle_pointer_press(PointF position); bool handle_pointer_move(PointF position); bool handle_pointer_release(PointF position); + bool handle_pointer_leave(); void set_update_callback(std::function callback); void set_clipboard_callback(std::function callback); diff --git a/Core/plottable/Performance_Overlay_p.h b/Core/plottable/Performance_Overlay_p.h index 7b13466..5122662 100644 --- a/Core/plottable/Performance_Overlay_p.h +++ b/Core/plottable/Performance_Overlay_p.h @@ -23,6 +23,7 @@ #include "../base/Style.h" #include "../base/String_Format.h" #include "../base/Text.h" +#include "../render/Button.h" #include "../render/Canvas.h" #include "Performance_Overlay.h" #include "Psc_Cpp_Core/Statistics/Statistics.h" @@ -383,6 +384,8 @@ struct Performance_Worker_State { std::atomic_bool cancelled{false}; std::atomic_bool reset_requested{false}; std::atomic_bool snapshot_rebuild_requested{false}; + std::atomic copy_button_state{Button_State::Normal}; + std::atomic close_button_state{Button_State::Normal}; std::atomic scroll_offset{0.0}; std::atomic pending_wheel_delta_px{0.0}; std::atomic pending_page_delta{0}; @@ -453,11 +456,9 @@ struct Performance_Layout_Builder { RectF panel_rect; RectF scroll_bar_rect; RectF scroll_thumb_rect; - RectF copy_button_rect; - RectF close_button_rect; + Button copy_button{"Copy"}; + Button close_button{"Close"}; double content_width{}; - double copy_button_width{}; - double close_button_width{}; double toolbar_height{}; double button_gap{4.0}; static std::string value(double number, int precision = 2) { @@ -476,22 +477,22 @@ struct Performance_Layout_Builder { return std::max(0.0, height - text_top(state) - state->bottom_margin); } void update_toolbar_button_rects(Performance_Overlay_Render_State* state) { - copy_button_rect = {}; - close_button_rect = {}; + copy_button.set_rect({}); + close_button.set_rect({}); if (panel_rect.empty() || toolbar_height <= 0.0) return; double right = panel_rect.width - static_cast(state->right_margin + state->scroll_bar_width + state->scroll_bar_margin * 2); double left = static_cast(state->left_margin); double available_width = std::max(0.0, right - left); - double close_width = std::min(close_button_width, available_width); + double close_width = std::min(close_button.preferred_width(), available_width); if (close_width <= 0.0) return; - close_button_rect = RectF{right - close_width, static_cast(state->top_margin), close_width, toolbar_height}; - available_width = std::max(0.0, close_button_rect.x - button_gap - left); - double copy_width = std::min(copy_button_width, available_width); + close_button.set_rect(RectF{right - close_width, static_cast(state->top_margin), close_width, toolbar_height}); + available_width = std::max(0.0, close_button.rect().x - button_gap - left); + double copy_width = std::min(copy_button.preferred_width(), available_width); if (copy_width <= 0.0) return; - copy_button_rect = RectF{close_button_rect.x - button_gap - copy_width, static_cast(state->top_margin), copy_width, toolbar_height}; + copy_button.set_rect(RectF{close_button.rect().x - button_gap - copy_width, static_cast(state->top_margin), copy_width, toolbar_height}); } void update_scroll_bar_rects(Performance_Overlay_Render_State* state, double max_scroll, double scroll, double visible_height, double total_height) { if (max_scroll <= 0.0 || panel_rect.width <= 0.0 || panel_rect.height <= 0.0 || total_height <= 0.0) { @@ -1065,7 +1066,7 @@ struct Performance_Layout_Builder { double max_width = 0.0; for (const auto& line : render_lines) max_width = std::max(max_width, line.width); - max_width = std::max(max_width, copy_button_width + button_gap + close_button_width); + max_width = std::max(max_width, copy_button.preferred_width() + button_gap + close_button.preferred_width()); double measured_width = max_width + static_cast(state->left_margin + state->right_margin + state->scroll_bar_width + state->scroll_bar_margin * 2); held_width = std::max(held_width, measured_width); @@ -1231,6 +1232,8 @@ struct Performance_Overlay_Private { Performance_Log_Plot_Identity log_identity; mutable std::mutex clipboard_callback_mutex; std::function clipboard_callback; + Button_Interaction copy_button_interaction; + Button_Interaction close_button_interaction; Performance_Overlay_Private() : metrics_worker(std::make_shared()) {} @@ -1278,6 +1281,23 @@ struct Performance_Overlay_Private { callback(std::move(text)); } + void publish_button_states(bool changed) { + if (!changed) + return; + metrics_worker->copy_button_state.store(copy_button_interaction.state(), std::memory_order_release); + metrics_worker->close_button_state.store(close_button_interaction.state(), std::memory_order_release); + metrics_worker->snapshot_rebuild_requested.store(true, std::memory_order_release); + if (enabled_value.load(std::memory_order_acquire)) + schedule_performance_metrics_worker(metrics_worker); + } + + void reset_button_states() { + copy_button_interaction.reset(); + close_button_interaction.reset(); + metrics_worker->copy_button_state.store(Button_State::Normal, std::memory_order_release); + metrics_worker->close_button_state.store(Button_State::Normal, std::memory_order_release); + } + static const char* feedback_outcome_event(Frame_Outcome outcome) { switch (outcome) { case Frame_Outcome::Presented: @@ -1379,11 +1399,16 @@ struct Performance_Overlay_Private { metrics = measure_canvas.measure_text("0123456789 ABC \xE4\xB8\xAD\xE6\x96\x87"); if (metrics.line_height() <= 0.0) metrics = measure_canvas.measure_text("M"); - Text_Metrics copy_metrics = measure_canvas.measure_text("Copy"); - Text_Metrics close_metrics = measure_canvas.measure_text("Close"); - layout.copy_button_width = std::max(48.0, copy_metrics.width + 16.0); - layout.close_button_width = std::max(48.0, close_metrics.width + 16.0); - layout.toolbar_height = std::max(20.0, metrics.line_height() + 4.0); + Button_Style close_style = layout.close_button.style(); + close_style.text = state.warning_color; + close_style.hover_text = state.warning_color; + close_style.pressed_text = state.warning_color; + layout.close_button.set_style(std::move(close_style)); + layout.copy_button.set_state(worker_state.copy_button_state.load(std::memory_order_acquire)); + layout.close_button.set_state(worker_state.close_button_state.load(std::memory_order_acquire)); + layout.copy_button.measure(measure_canvas); + layout.close_button.measure(measure_canvas); + layout.toolbar_height = std::max(layout.copy_button.preferred_height(), layout.close_button.preferred_height()); layout.seed_static_field_widths(measure_canvas); layout.build_render_lines(measure_canvas); } @@ -1427,22 +1452,8 @@ struct Performance_Overlay_Private { layout.draw_render_line(canvas, layout.render_lines[static_cast(i)], text_rect.x, y, line_height, state); } canvas.reset_clip(); - if (!layout.copy_button_rect.empty()) { - canvas.set_brush(Brush{state.scroll_track, Brush_Style::Solid}); - canvas.fill_rect(layout.copy_button_rect); - canvas.set_pen(Pen{state.scroll_thumb}); - canvas.draw_rect(layout.copy_button_rect); - canvas.set_pen(Pen{state.foreground}); - canvas.draw_text(layout.copy_button_rect, Text_Align::Center, "Copy"); - } - if (!layout.close_button_rect.empty()) { - canvas.set_brush(Brush{state.scroll_track, Brush_Style::Solid}); - canvas.fill_rect(layout.close_button_rect); - canvas.set_pen(Pen{state.scroll_thumb}); - canvas.draw_rect(layout.close_button_rect); - canvas.set_pen(Pen{state.warning_color}); - canvas.draw_text(layout.close_button_rect, Text_Align::Center, "Close"); - } + layout.copy_button.draw(canvas); + layout.close_button.draw(canvas); layout.update_scroll_bar_rects(&state, max_scroll, scroll, content_viewport_height, text_content_height); if (!layout.scroll_bar_rect.empty()) { canvas.set_brush(Brush{state.scroll_track, Brush_Style::Solid}); @@ -1454,8 +1465,8 @@ struct Performance_Overlay_Private { snapshot->destination_rect = layout.panel_rect; snapshot->scroll_track_rect = layout.scroll_bar_rect; snapshot->scroll_thumb_rect = layout.scroll_thumb_rect; - snapshot->copy_button_rect = layout.copy_button_rect; - snapshot->close_button_rect = layout.close_button_rect; + snapshot->copy_button_rect = layout.copy_button.rect(); + snapshot->close_button_rect = layout.close_button.rect(); snapshot->content_height = content_height; snapshot->scroll_offset = scroll; snapshot->max_scroll_offset = max_scroll; diff --git a/Core/plottable/export.h b/Core/plottable/export.h index 46adfb7..e5cb14f 100644 --- a/Core/plottable/export.h +++ b/Core/plottable/export.h @@ -29,4 +29,5 @@ LIB_DECL bool performance_overlay_handle_wheel(Plot_Core& plot, PointF position, LIB_DECL bool performance_overlay_handle_pointer_press(Plot_Core& plot, PointF position); LIB_DECL bool performance_overlay_handle_pointer_move(Plot_Core& plot, PointF position); LIB_DECL bool performance_overlay_handle_pointer_release(Plot_Core& plot, PointF position); +LIB_DECL bool performance_overlay_handle_pointer_leave(Plot_Core& plot); } // namespace renderive diff --git a/Core/render/Button.h b/Core/render/Button.h new file mode 100644 index 0000000..54716a7 --- /dev/null +++ b/Core/render/Button.h @@ -0,0 +1,164 @@ +#pragma once +#include "../base/Color.h" +#include "../base/Geometry.h" +#include "../base/Style.h" +#include "Canvas.h" +#include +#include +#include +#include +namespace renderive { +enum class Button_State : std::uint8_t { + Normal, + Hovered, + Pressed +}; +struct Button_Style { + Color background{48, 48, 48}; + Color hover_background{68, 68, 68}; + Color pressed_background{32, 32, 32}; + Color border{96, 96, 96}; + Color hover_border{64, 142, 255}; + Color pressed_border{32, 104, 220}; + Color text{235, 235, 235}; + Color hover_text{255, 255, 255}; + Color pressed_text{255, 255, 255}; + double horizontal_padding{8.0}; + double vertical_padding{2.0}; + double minimum_width{48.0}; + double minimum_height{20.0}; +}; +struct Button_Event_Result { + bool consumed{}; + bool changed{}; + bool clicked{}; +}; +class Button { +public: + Button() = default; + explicit Button(std::string text) + : text_(std::move(text)) {} + void set_text(std::string text) { + text_ = std::move(text); + } + const std::string& text() const { + return text_; + } + void set_style(Button_Style style) { + style_ = std::move(style); + } + const Button_Style& style() const { + return style_; + } + void set_rect(RectF rect) { + rect_ = rect; + } + const RectF& rect() const { + return rect_; + } + void set_state(Button_State state) { + state_ = state; + } + Button_State state() const { + return state_; + } + void measure(const Canvas& canvas) { + Text_Metrics metrics = canvas.measure_text(text_); + preferred_width_ = std::max(style_.minimum_width, metrics.width + style_.horizontal_padding * 2.0); + preferred_height_ = std::max(style_.minimum_height, metrics.line_height() + style_.vertical_padding * 2.0); + } + double preferred_width() const { + return preferred_width_; + } + double preferred_height() const { + return preferred_height_; + } + bool contains(PointF position) const { + return !rect_.empty() && rect_.contains(position); + } + void draw(Canvas& canvas) const { + if (rect_.empty()) + return; + canvas.set_brush(Brush{background_color(), Brush_Style::Solid}); + canvas.fill_rect(rect_); + canvas.set_pen(Pen{border_color()}); + canvas.draw_rect(rect_); + canvas.set_pen(Pen{text_color()}); + canvas.draw_text(rect_, Text_Align::Center, text_); + } +private: + Color background_color() const { + if (state_ == Button_State::Pressed) + return style_.pressed_background; + if (state_ == Button_State::Hovered) + return style_.hover_background; + return style_.background; + } + Color border_color() const { + if (state_ == Button_State::Pressed) + return style_.pressed_border; + if (state_ == Button_State::Hovered) + return style_.hover_border; + return style_.border; + } + Color text_color() const { + if (state_ == Button_State::Pressed) + return style_.pressed_text; + if (state_ == Button_State::Hovered) + return style_.hover_text; + return style_.text; + } + std::string text_; + Button_Style style_; + RectF rect_; + Button_State state_{Button_State::Normal}; + double preferred_width_{}; + double preferred_height_{}; +}; +class Button_Interaction { +public: + Button_Event_Result pointer_move(PointF position, const RectF& rect) { + bool inside = !rect.empty() && rect.contains(position); + Button_State next = pressed_ && inside ? Button_State::Pressed : inside ? Button_State::Hovered : Button_State::Normal; + bool changed = set_state(next); + return {inside || pressed_ || changed, changed, false}; + } + Button_Event_Result pointer_press(PointF position, const RectF& rect) { + if (rect.empty() || !rect.contains(position)) + return {}; + pressed_ = true; + bool changed = set_state(Button_State::Pressed); + return {true, changed, false}; + } + Button_Event_Result pointer_release(PointF position, const RectF& rect) { + bool inside = !rect.empty() && rect.contains(position); + if (!pressed_) { + bool changed = set_state(inside ? Button_State::Hovered : Button_State::Normal); + return {false, changed, false}; + } + pressed_ = false; + bool changed = set_state(inside ? Button_State::Hovered : Button_State::Normal); + return {true, changed, inside}; + } + Button_Event_Result pointer_leave() { + bool changed = set_state(Button_State::Normal); + return {pressed_ || changed, changed, false}; + } + bool reset() { + pressed_ = false; + return set_state(Button_State::Normal); + } + Button_State state() const { + return state_; + } +private: + bool set_state(Button_State state) { + if (state_ == state) + return false; + state_ = state; + return true; + } + bool pressed_{}; + Button_State state_{Button_State::Normal}; +}; +} // namespace renderive diff --git a/Demo_Gallery.zip b/Demo_Gallery.zip deleted file mode 100644 index f5aa2c8..0000000 Binary files a/Demo_Gallery.zip and /dev/null differ diff --git a/Qt/plot/Plot.cpp b/Qt/plot/Plot.cpp index 110938c..10d8826 100644 --- a/Qt/plot/Plot.cpp +++ b/Qt/plot/Plot.cpp @@ -190,6 +190,8 @@ bool Abs_Plot_Private::event(Abs_Plot* plot, QEvent* event, bool base_result) { break; } case QEvent::Leave: { + if (performance_overlay_handle_pointer_leave(*core)) + request_performance_overlay_update(plot); Event leave_event(Event_Type::Leave); core->dispatch_event(leave_event); break;