379 lines
14 KiB
Rust
379 lines
14 KiB
Rust
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
|
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
|
|
|
|
use eframe::egui;
|
|
use eframe::egui::{Rangef, Shape, UiKind};
|
|
use egui_extras::Column;
|
|
|
|
fn main() -> eframe::Result {
|
|
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
|
let options = eframe::NativeOptions {
|
|
viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
|
|
..Default::default()
|
|
};
|
|
eframe::run_native(
|
|
"Stack Frame Demo",
|
|
options,
|
|
Box::new(|cc| {
|
|
// This gives us image support:
|
|
egui_extras::install_image_loaders(&cc.egui_ctx);
|
|
|
|
Ok(Box::<MyApp>::default())
|
|
}),
|
|
)
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct MyApp {
|
|
show_settings: bool,
|
|
show_inspection: bool,
|
|
show_memory: bool,
|
|
}
|
|
|
|
impl eframe::App for MyApp {
|
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
|
ctx.all_styles_mut(|style| style.interaction.tooltip_delay = 0.0);
|
|
|
|
egui::SidePanel::left("side_panel_left").show(ctx, |ui| {
|
|
ui.heading("Information");
|
|
ui.label(
|
|
"This is a demo/test environment of the `UiStack` feature. The tables display \
|
|
the UI stack in various contexts. You can hover on the IDs to display the \
|
|
corresponding origin/`max_rect`.\n\n\
|
|
The \"Full span test\" labels showcase an implementation of full-span \
|
|
highlighting. Hover to see them in action!",
|
|
);
|
|
ui.add_space(10.0);
|
|
ui.checkbox(&mut self.show_settings, "🔧 Settings");
|
|
ui.checkbox(&mut self.show_inspection, "🔍 Inspection");
|
|
ui.checkbox(&mut self.show_memory, "📝 Memory");
|
|
ui.add_space(10.0);
|
|
if ui.button("Reset egui memory").clicked() {
|
|
ctx.memory_mut(|mem| *mem = Default::default());
|
|
}
|
|
ui.add_space(20.0);
|
|
|
|
egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
|
|
stack_ui(ui);
|
|
|
|
// full span test
|
|
ui.add_space(20.0);
|
|
full_span_widget(ui, false);
|
|
|
|
// nested frames test
|
|
ui.add_space(20.0);
|
|
egui::Frame::new()
|
|
.stroke(ui.visuals().noninteractive().bg_stroke)
|
|
.inner_margin(4)
|
|
.outer_margin(4)
|
|
.show(ui, |ui| {
|
|
full_span_widget(ui, false);
|
|
stack_ui(ui);
|
|
|
|
egui::Frame::new()
|
|
.stroke(ui.visuals().noninteractive().bg_stroke)
|
|
.inner_margin(8)
|
|
.outer_margin(6)
|
|
.show(ui, |ui| {
|
|
full_span_widget(ui, false);
|
|
stack_ui(ui);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
egui::SidePanel::right("side_panel_right").show(ctx, |ui| {
|
|
egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
|
|
stack_ui(ui);
|
|
|
|
// full span test
|
|
ui.add_space(20.0);
|
|
full_span_widget(ui, false);
|
|
});
|
|
});
|
|
|
|
egui::CentralPanel::default().show(ctx, |ui| {
|
|
egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
|
|
ui.label("stack here:");
|
|
stack_ui(ui);
|
|
|
|
// full span test
|
|
ui.add_space(20.0);
|
|
full_span_widget(ui, false);
|
|
|
|
// tooltip test
|
|
ui.add_space(20.0);
|
|
ui.label("Hover me").on_hover_ui(|ui| {
|
|
full_span_widget(ui, true);
|
|
ui.add_space(20.0);
|
|
stack_ui(ui);
|
|
});
|
|
|
|
// combobox test
|
|
ui.add_space(20.0);
|
|
egui::ComboBox::from_id_salt("combo_box")
|
|
.selected_text("click me")
|
|
.show_ui(ui, |ui| {
|
|
full_span_widget(ui, true);
|
|
ui.add_space(20.0);
|
|
stack_ui(ui);
|
|
});
|
|
|
|
// Ui nesting test
|
|
ui.add_space(20.0);
|
|
ui.label("UI nesting test:");
|
|
egui::Frame::new()
|
|
.stroke(ui.visuals().noninteractive().bg_stroke)
|
|
.inner_margin(4)
|
|
.show(ui, |ui| {
|
|
ui.horizontal(|ui| {
|
|
ui.vertical(|ui| {
|
|
ui.scope(stack_ui);
|
|
});
|
|
});
|
|
});
|
|
|
|
// table test
|
|
let mut cell_stack = None;
|
|
ui.add_space(20.0);
|
|
ui.label("Table test:");
|
|
|
|
egui_extras::TableBuilder::new(ui)
|
|
.vscroll(false)
|
|
.column(Column::auto())
|
|
.column(Column::auto())
|
|
.header(20.0, |mut header| {
|
|
header.col(|ui| {
|
|
ui.strong("column 1");
|
|
});
|
|
header.col(|ui| {
|
|
ui.strong("column 2");
|
|
});
|
|
})
|
|
.body(|mut body| {
|
|
body.row(20.0, |mut row| {
|
|
row.col(|ui| {
|
|
full_span_widget(ui, false);
|
|
});
|
|
row.col(|ui| {
|
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
|
ui.label("See stack below");
|
|
cell_stack = Some(ui.stack().clone());
|
|
});
|
|
});
|
|
});
|
|
|
|
if let Some(cell_stack) = cell_stack {
|
|
ui.label("Cell's stack:");
|
|
stack_ui_impl(ui, &cell_stack);
|
|
}
|
|
});
|
|
});
|
|
|
|
egui::TopBottomPanel::bottom("bottom_panel")
|
|
.resizable(true)
|
|
.show(ctx, |ui| {
|
|
egui::ScrollArea::vertical()
|
|
.auto_shrink(false)
|
|
.show(ui, |ui| {
|
|
stack_ui(ui);
|
|
|
|
// full span test
|
|
ui.add_space(20.0);
|
|
full_span_widget(ui, false);
|
|
});
|
|
});
|
|
|
|
egui::Window::new("Window")
|
|
.pivot(egui::Align2::RIGHT_TOP)
|
|
.show(ctx, |ui| {
|
|
full_span_widget(ui, false);
|
|
ui.add_space(20.0);
|
|
stack_ui(ui);
|
|
});
|
|
|
|
egui::Window::new("🔧 Settings")
|
|
.open(&mut self.show_settings)
|
|
.vscroll(true)
|
|
.show(ctx, |ui| {
|
|
ctx.settings_ui(ui);
|
|
});
|
|
|
|
egui::Window::new("🔍 Inspection")
|
|
.open(&mut self.show_inspection)
|
|
.vscroll(true)
|
|
.show(ctx, |ui| {
|
|
ctx.inspection_ui(ui);
|
|
});
|
|
|
|
egui::Window::new("📝 Memory")
|
|
.open(&mut self.show_memory)
|
|
.resizable(false)
|
|
.show(ctx, |ui| {
|
|
ctx.memory_ui(ui);
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Demo of a widget that highlights its background all the way to the edge of its container when
|
|
/// hovered.
|
|
fn full_span_widget(ui: &mut egui::Ui, permanent: bool) {
|
|
let bg_shape_idx = ui.painter().add(Shape::Noop);
|
|
let response = ui.label("Full span test");
|
|
let ui_stack = ui.stack();
|
|
|
|
let rect = egui::Rect::from_x_y_ranges(
|
|
full_span_horizontal_range(ui_stack),
|
|
response.rect.y_range(),
|
|
);
|
|
|
|
if permanent || response.hovered() {
|
|
ui.painter().set(
|
|
bg_shape_idx,
|
|
Shape::rect_filled(rect, 0.0, ui.visuals().selection.bg_fill),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Find the horizontal range of the enclosing container.
|
|
fn full_span_horizontal_range(ui_stack: &egui::UiStack) -> Rangef {
|
|
for node in ui_stack.iter() {
|
|
if node.has_visible_frame()
|
|
|| node.is_panel_ui()
|
|
|| node.is_root_ui()
|
|
|| node.kind() == Some(UiKind::TableCell)
|
|
{
|
|
return (node.max_rect + node.frame().inner_margin).x_range();
|
|
}
|
|
}
|
|
|
|
// should never happen
|
|
Rangef::EVERYTHING
|
|
}
|
|
|
|
fn stack_ui(ui: &mut egui::Ui) {
|
|
let ui_stack = ui.stack().clone();
|
|
ui.scope(|ui| {
|
|
stack_ui_impl(ui, &ui_stack);
|
|
});
|
|
}
|
|
|
|
fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) {
|
|
egui::Frame::new()
|
|
.stroke(ui.style().noninteractive().fg_stroke)
|
|
.inner_margin(4)
|
|
.show(ui, |ui| {
|
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
|
|
|
egui_extras::TableBuilder::new(ui)
|
|
.column(Column::auto())
|
|
.column(Column::auto())
|
|
.column(Column::auto())
|
|
.column(Column::auto())
|
|
.column(Column::auto())
|
|
.column(Column::auto())
|
|
.header(20.0, |mut header| {
|
|
header.col(|ui| {
|
|
ui.strong("id");
|
|
});
|
|
header.col(|ui| {
|
|
ui.strong("kind");
|
|
});
|
|
header.col(|ui| {
|
|
ui.strong("stroke");
|
|
});
|
|
header.col(|ui| {
|
|
ui.strong("inner");
|
|
});
|
|
header.col(|ui| {
|
|
ui.strong("outer");
|
|
});
|
|
header.col(|ui| {
|
|
ui.strong("direction");
|
|
});
|
|
})
|
|
.body(|mut body| {
|
|
for node in stack.iter() {
|
|
body.row(20.0, |mut row| {
|
|
row.col(|ui| {
|
|
if ui.label(format!("{:?}", node.id)).hovered() {
|
|
ui.ctx().debug_painter().debug_rect(
|
|
node.max_rect,
|
|
egui::Color32::GREEN,
|
|
"max",
|
|
);
|
|
ui.ctx().debug_painter().circle_filled(
|
|
node.min_rect.min,
|
|
2.0,
|
|
egui::Color32::RED,
|
|
);
|
|
}
|
|
});
|
|
row.col(|ui| {
|
|
let s = if let Some(kind) = node.kind() {
|
|
format!("{kind:?}")
|
|
} else {
|
|
"-".to_owned()
|
|
};
|
|
|
|
ui.label(s);
|
|
});
|
|
row.col(|ui| {
|
|
let frame = node.frame();
|
|
if frame.stroke == egui::Stroke::NONE {
|
|
ui.label("-");
|
|
} else {
|
|
let mut layout_job = egui::text::LayoutJob::default();
|
|
layout_job.append(
|
|
"⬛ ",
|
|
0.0,
|
|
egui::TextFormat::simple(
|
|
egui::TextStyle::Body.resolve(ui.style()),
|
|
frame.stroke.color,
|
|
),
|
|
);
|
|
layout_job.append(
|
|
format!("{}px", frame.stroke.width).as_str(),
|
|
0.0,
|
|
egui::TextFormat::simple(
|
|
egui::TextStyle::Body.resolve(ui.style()),
|
|
ui.style().visuals.text_color(),
|
|
),
|
|
);
|
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
|
ui.label(layout_job);
|
|
}
|
|
});
|
|
row.col(|ui| {
|
|
ui.label(print_margin(&node.frame().inner_margin));
|
|
});
|
|
row.col(|ui| {
|
|
ui.label(print_margin(&node.frame().outer_margin));
|
|
});
|
|
row.col(|ui| {
|
|
ui.label(format!("{:?}", node.layout_direction));
|
|
});
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
fn print_margin(margin: &egui::Margin) -> String {
|
|
if margin.is_same() {
|
|
format!("{}px", margin.left)
|
|
} else {
|
|
let s1 = if margin.left == margin.right {
|
|
format!("H: {}px", margin.left)
|
|
} else {
|
|
format!("L: {}px R: {}px", margin.left, margin.right)
|
|
};
|
|
let s2 = if margin.top == margin.bottom {
|
|
format!("V: {}px", margin.top)
|
|
} else {
|
|
format!("T: {}px B: {}px", margin.top, margin.bottom)
|
|
};
|
|
format!("{s1} / {s2}")
|
|
}
|
|
}
|