egui/tests/test_ui_stack/src/main.rs

349 lines
13 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() -> Result<(), eframe::Error> {
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 {}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
ctx.style_mut(|style| style.interaction.tooltip_delay = 0.0);
egui::SidePanel::right("side_panel").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);
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 {
stroke: ui.visuals().noninteractive().bg_stroke,
inner_margin: egui::Margin::same(4.0),
outer_margin: egui::Margin::same(4.0),
..Default::default()
}
.show(ui, |ui| {
full_span_widget(ui, false);
stack_ui(ui);
egui::Frame {
stroke: ui.visuals().noninteractive().bg_stroke,
inner_margin: egui::Margin::same(8.0),
outer_margin: egui::Margin::same(6.0),
..Default::default()
}
.show(ui, |ui| {
full_span_widget(ui, false);
stack_ui(ui);
});
});
});
});
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::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::vertical()
.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_source("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 {
stroke: ui.visuals().noninteractive().bg_stroke,
inner_margin: egui::Margin::same(4.0),
..Default::default()
}
.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::Window::new("Window")
.pivot(egui::Align2::RIGHT_TOP)
.show(ctx, |ui| {
full_span_widget(ui, false);
ui.add_space(20.0);
stack_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 {
stroke: ui.style().noninteractive().fg_stroke,
inner_margin: egui::Margin::same(4.0),
..Default::default()
}
.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}")
}
}