Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 53 additions & 75 deletions gui/src/search/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod query_window;
pub mod segmented_button;
use std::{
cell::RefCell,
collections::HashMap,
Expand All @@ -11,12 +12,16 @@ use std::{
};

use crate::{
ApiDocsState, LogState, TraceProvider, icon_colored, notifications::draw_x, rect,
search::query_window::PaginatedResults, spawn_task,
ApiDocsState, LogState, TraceProvider, icon_colored,
notifications::draw_x,
rect,
search::{query_window::PaginatedResults, segmented_button::SegmentedIconButtons},
spawn_task,
};
use crossbeam::channel::Receiver;
use egui::{
Color32, CornerRadius, Margin, Pos2, Rect, Response, Sense, Separator, TextEdit, Ui, pos2, vec2,
Color32, CornerRadius, Margin, Pos2, Rect, Response, Sense, Separator, TextEdit, Ui,
epaint::RectShape, pos2, vec2,
};
use entrace_core::LogProviderError;
use entrace_query::{
Expand Down Expand Up @@ -287,6 +292,7 @@ pub fn bottom_panel_ui(
let total_top_padding = resize_width + text_field_margin.topf();
let search_rect = search_response.rect;
let search_rect = search_rect.with_min_y(search_rect.min.y - total_top_padding);

let icon_size = 20.0;
let rect_top_left = pos2(avail.max.x - (3.0 * icon_size), search_rect.min.y);
let rect_bottom_right = pos2(avail.max.x, search_rect.min.y + icon_size);
Expand All @@ -296,42 +302,14 @@ pub fn bottom_panel_ui(
egui::Theme::Dark => Color32::DARK_GRAY,
egui::Theme::Light => Color32::LIGHT_GRAY,
};
ui.painter().rect_filled(rect2, bg_corner_radius, color);

// make sure the items we add do not overflow the bottom panel, since that will
// grow it. to do this, we define an inner rect.
let spacing = 3.0;
let inner_rect_min = rect2.min + vec2(3.0, 3.0);
let inner_rect_max = rect2.max + vec2(-1.0, -3.0);
let total_width = inner_rect_max.x - inner_rect_min.x;
// [<left><spacing>|<spacing><mid><spacing>|<spacing><right>]
let segment_width = (total_width - (2.0 * spacing)) / 3.0;
let segment_size = vec2(segment_width, inner_rect_max.y - inner_rect_min.y);
let inner_left = Rect::from_min_size(inner_rect_min, segment_size);
let inner_mid =
Rect::from_min_size(pos2(inner_left.max.x + spacing, inner_rect_min.y), segment_size);
let inner_right = rect![pos2(inner_mid.max.x + spacing, inner_rect_min.y), inner_rect_max];
let bg_left = rect![rect2.min, pos2(inner_left.max.x, rect2.max.y)];
let bg_mid = rect![pos2(inner_mid.min.x, rect2.min.y), pos2(inner_mid.max.x, rect2.max.y)];
let bg_right = rect![pos2(inner_right.min.x, rect2.min.y), rect2.max];

let (topy, bottomy) = (rect2.min.y + 3.0, rect2.max.y - 1.0);
let sep1_rect =
rect![pos2(inner_left.max.x + spacing, topy), pos2(inner_mid.min.x - spacing, bottomy)];
let sep2_rect =
rect![pos2(inner_mid.max.x + spacing, topy), pos2(inner_right.min.x - spacing, bottomy)];
ui.put(sep1_rect, Separator::default().vertical());
ui.put(sep2_rect, Separator::default().vertical());

fn paint_label(
fn paint_label<L, O>(
ui: &mut Ui, bg_rect: Rect, bg_corner_radius: CornerRadius, inner_rect: Rect,
label_callback: impl FnOnce(&mut Ui, Color32), on_click: impl FnOnce(Response),
hover_text: Option<&str>,
label_callback: impl FnOnce(&mut Ui, Color32) -> L, on_click: impl FnOnce(Response) -> O,
hover_text: &str,
) {
let mut resp = ui.allocate_rect(inner_rect, Sense::click());
if let Some(x) = hover_text {
resp = resp.on_hover_text(x);
}
resp = resp.on_hover_text(hover_text);
if resp.hovered() {
ui.painter().rect_filled(bg_rect, bg_corner_radius, Color32::GRAY.gamma_multiply(0.5));
}
Expand All @@ -342,46 +320,46 @@ pub fn bottom_panel_ui(
on_click(resp);
}
}
paint_label(
ui,
bg_left,
bg_corner_radius,
inner_left,
|ui, color| {
ui.put(inner_left, icon_colored!("../../vendor/icons/play_arrow.svg", color));
},
|_| search_state.new_query(log_state.trace_provider.clone()),
Some("Run (Ctrl+Enter)"),
);
paint_label(
ui,
bg_mid,
bg_corner_radius,
inner_mid,
|ui, color| {
ui.put(inner_mid, icon_colored!("../../vendor/icons/docs.svg", color));
},
|_| {
api_docs_state.open = true;
},
Some("Lua API Docs"),
);

paint_label(
ui,
bg_right,
CornerRadius::ZERO,
inner_right,
|ui, color| {
ui.put(inner_right, icon_colored!("../../vendor/icons/settings.svg", color));
},
|_| {
info!(settings_btn_rect = ?inner_right, "Query settings icon clicked");
search_state.settings.data =
QuerySettingsDialogData::Open { settings_button_rect: inner_right, position: None }
},
Some("Settings"),
);
let inner_to_bg_rect =
|inner: Rect| rect![pos2(inner.min.x, rect2.min.y), pos2(inner.max.x, rect2.max.y)];
SegmentedIconButtons::new(RectShape::filled(rect2, bg_corner_radius, color))
.separator_y_padding([3.0, 1.0])
.with_contents(|ui, rects: [Rect; 3]| {
paint_label(
ui,
inner_to_bg_rect(rects[0]).with_min_x(rect2.min.x),
bg_corner_radius,
rects[0],
|ui, clr| ui.put(rects[0], icon_colored!("../../vendor/icons/play_arrow.svg", clr)),
|_| search_state.new_query(log_state.trace_provider.clone()),
"Run (Ctrl+Enter)",
);
paint_label(
ui,
inner_to_bg_rect(rects[1]),
CornerRadius::ZERO,
rects[1],
|ui, clr| ui.put(rects[1], icon_colored!("../../vendor/icons/docs.svg", clr)),
|_| api_docs_state.open = true,
"Lua API Docs",
);
paint_label(
ui,
inner_to_bg_rect(rect![rects[2].min, rect2.max]),
CornerRadius::ZERO,
rects[2],
|ui, clr| ui.put(rects[2], icon_colored!("../../vendor/icons/settings.svg", clr)),
|_| {
info!(settings_btn_rect = ?rects[2], "Query settings icon clicked");
search_state.settings.data = QuerySettingsDialogData::Open {
settings_button_rect: rects[2],
position: None,
}
},
"Settings",
);
})
.show(ui);
}
pub struct LocatingStarted {
pub target: u32,
Expand Down
123 changes: 123 additions & 0 deletions gui/src/search/segmented_button.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use crate::rect;
use egui::{Color32, Rect, Sense, Separator, Ui, Vec2, epaint::RectShape, pos2, vec2};

pub struct SegmentedIconButtons<const N: usize, F = ()> {
shape: RectShape,
/// controls the spacing between the segments inside the container. 3.0 by default.
inner_spacing: f32,
/// the area for the inner rect. by default rect![shape.rect.min + spacing2, shape.rect.max - spacing2]
inner_rect: Rect,
add_contents: F,
separator_padding_y: [f32; 2],
}

impl<const N: usize> SegmentedIconButtons<N, ()> {
pub fn new(shape: RectShape) -> Self {
let rect = shape.rect;
let spacing2 = Vec2::splat(3.0);
let inner_rect = rect![rect.min + spacing2, rect.max - spacing2];
Self {
shape,
inner_spacing: 3.0,
add_contents: (),
inner_rect,
separator_padding_y: [0.0; 2],
}
}
}

impl<const N: usize, F> SegmentedIconButtons<N, F> {
pub fn inner_spacing(mut self, spacing: f32) -> Self {
self.inner_spacing = spacing;
self
}
pub fn inner_rect(mut self, inner_rect: Rect) -> Self {
self.inner_rect = inner_rect;
self
}

pub fn with_contents<R, G>(self, add_contents: G) -> SegmentedIconButtons<N, G>
where
G: FnOnce(&mut Ui, [Rect; N]) -> R,
{
SegmentedIconButtons {
shape: self.shape,
inner_spacing: self.inner_spacing,
add_contents,
inner_rect: self.inner_rect,
separator_padding_y: self.separator_padding_y,
}
}

pub fn separator_y_padding(mut self, separator_y_padding: [f32; 2]) -> Self {
self.separator_padding_y = separator_y_padding;
self
}
}

impl<const N: usize, R, F> SegmentedIconButtons<N, F>
where
F: FnOnce(&mut Ui, [Rect; N]) -> R,
{
pub fn show(self, ui: &mut Ui) {
if N == 0 {
return;
}

let rect = self.shape.rect;
ui.painter().add(self.shape);
let mut rects = [Rect::NOTHING; N];

let total_width = self.inner_rect.width();
let segment_width =
(total_width - ((N - 1) as f32 * self.inner_spacing)) / (N as f32).max(1.0);
let segment_size = vec2(segment_width, self.inner_rect.height());

let (topy, bottomy, inner) = (
rect.min.y + self.separator_padding_y[0],
rect.max.y - self.separator_padding_y[1],
&self.inner_rect,
);
let mut last_x = self.inner_rect.min.x;
for (i, rect) in rects.iter_mut().enumerate() {
*rect = if i == N - 1 {
rect![pos2(last_x, inner.min.y), inner.max]
} else {
Rect::from_min_size(pos2(last_x, inner.min.y), segment_size)
};

if i > 0 {
let sep_rect =
rect![pos2(last_x - self.inner_spacing, topy), pos2(last_x, bottomy)];
ui.put(sep_rect, Separator::default().vertical());
}
last_x = rect.max.x + self.inner_spacing;
}

let _inner = (self.add_contents)(ui, rects);
}
}

pub fn demo(ui: &mut Ui) {
let (rect, _) = ui.allocate_at_least(vec2(150.0, 24.0), Sense::hover());
let mut shape = RectShape::filled(rect, 4.0, ui.visuals().widgets.inactive.bg_fill);
shape.stroke = ui.style().visuals.widgets.inactive.bg_stroke;

SegmentedIconButtons::new(shape.clone())
.inner_rect(shape.rect)
.with_contents(|ui, rects: [Rect; 5]| {
for (i, r) in rects.into_iter().enumerate() {
let label = (i + 1).to_string();
let resp = ui.put(r, egui::Button::new(label).frame(false));

if resp.hovered() {
ui.painter().rect_filled(r, 0.0, Color32::GRAY.gamma_multiply_u8(24));
}

if resp.clicked() {
println!("Clicked segment {}", i + 1);
}
}
})
.show(ui);
}
Loading