mirror of
https://github.com/emilk/egui.git
synced 2026-07-11 09:08:53 +00:00
bb8400853f
You can now set custom tags on the `UiStack`. This allows you to write code that is situationally aware at runtime. For instance, you could decide wether or not a label should truncate its text depending on what part of your ui it is in, without having to pass that info down via the callstack.
349 lines
13 KiB
Rust
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}")
|
|
}
|
|
}
|