Resizable panels + right and bottom panels (#438)

* Side panel resizing and add right panels

* Add resizable top/bottom panels

* Deprecate TopPanel

* Final tweaks and update CHANGELOG.md
This commit is contained in:
Emil Ernerfeldt 2021-05-26 22:06:10 +02:00 committed by GitHub
parent 196ddff499
commit c9766f8a7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 538 additions and 131 deletions

View file

@ -8,13 +8,20 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [
## Unreleased
### Added ⭐
* Add right and bottom panels (`SidePanel::right` and `Panel::bottom`).
* Add resizable panels.
* Add an option to overwrite frame of a `Panel`.
* Add `Style::override_text_style` to easily change the text style of everything in a `Ui` (or globally).
* You can now change `TextStyle` on checkboxes, radio buttons and `SelectableLabel`.
* Add support for [cint](https://crates.io/crates/cint) under `cint` feature.
* Add features `extra_asserts` and `extra_debug_asserts` to enable additional checks.
* Add an option to overwrite frame of `SidePanel` and `TopPanel`.
* `TextEdit` now supports edits on a generic buffer using `TextBuffer`.
### Changed 🔧
* `TopPanel::top` is now `TopBottomPanel::top`.
* `SidePanel::left` no longet takes the default width by argument, but by a builder call.
## 0.12.0 - 2021-05-10 - Multitouch, user memory, window pivots, and improved plots
### Added ⭐

View file

@ -282,7 +282,7 @@ impl Prepared {
// (except in rare cases where they don't fit).
// Adjust clip rect so we don't cast shadows on side panels:
let central_area = ctx.available_rect();
let is_within_central_area = central_area.contains(self.state.pos);
let is_within_central_area = central_area.contains_rect(self.state.rect().shrink(1.0));
if is_within_central_area {
clip_rect = clip_rect.intersect(central_area);
}

View file

@ -17,9 +17,12 @@ pub use {
collapsing_header::*,
combo_box::*,
frame::Frame,
panel::{CentralPanel, SidePanel, TopPanel},
panel::{CentralPanel, SidePanel, TopBottomPanel},
popup::*,
resize::Resize,
scroll_area::ScrollArea,
window::Window,
};
#[allow(deprecated)]
pub use panel::TopPanel;

View file

@ -4,41 +4,129 @@
//! the only places where you can put you widgets.
//!
//! The order in which you add panels matter!
//! The first panel you add will always be the outermost, and the last you add will always be the innermost.
//!
//! Add [`CentralPanel`] and [`Window`]:s last.
//! Always add any [`CentralPanel`] and [`Window`]:s last.
use std::ops::RangeInclusive;
use crate::*;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
struct PanelState {
rect: Rect,
}
// ----------------------------------------------------------------------------
/// A panel that covers the entire left side of the screen.
/// `Left` or `Right`
#[derive(Clone, Copy, Debug, PartialEq)]
enum Side {
Left,
Right,
}
impl Side {
fn opposite(self) -> Self {
match self {
Side::Left => Self::Right,
Side::Right => Self::Left,
}
}
fn set_rect_width(self, rect: &mut Rect, width: f32) {
match self {
Side::Left => rect.max.x = rect.min.x + width,
Side::Right => rect.min.x = rect.max.x - width,
}
}
fn side_x(self, rect: Rect) -> f32 {
match self {
Side::Left => rect.left(),
Side::Right => rect.right(),
}
}
}
/// A panel that covers the entire left or right side of the screen.
///
/// `SidePanel`s must be added before adding any [`CentralPanel`] or [`Window`]s.
/// The order in which you add panels matter!
/// The first panel you add will always be the outermost, and the last you add will always be the innermost.
///
/// Always add any [`CentralPanel`] and [`Window`]:s last.
///
/// ```
/// # let mut ctx = egui::CtxRef::default();
/// # ctx.begin_frame(Default::default());
/// # let ctx = &ctx;
/// egui::SidePanel::left("my_side_panel", 0.0).show(ctx, |ui| {
/// egui::SidePanel::left("my_left_panel").show(ctx, |ui| {
/// ui.label("Hello World!");
/// });
/// ```
///
/// See also [`TopBottomPanel`].
#[must_use = "You should call .show()"]
pub struct SidePanel {
side: Side,
id: Id,
max_width: f32,
frame: Option<Frame>,
resizable: bool,
default_width: f32,
width_range: RangeInclusive<f32>,
}
impl SidePanel {
/// `id_source`: Something unique, e.g. `"my_side_panel"`.
/// The given `max_width` is a soft maximum (as always), and the actual panel may be smaller or larger.
pub fn left(id_source: impl std::hash::Hash, max_width: f32) -> Self {
Self {
id: Id::new(id_source),
max_width,
frame: None,
/// `id_source`: Something unique, e.g. `"my_left_panel"`.
pub fn left(id_source: impl std::hash::Hash) -> Self {
Self::new(Side::Left, id_source)
}
/// `id_source`: Something unique, e.g. `"my_right_panel"`.
pub fn right(id_source: impl std::hash::Hash) -> Self {
Self::new(Side::Right, id_source)
}
/// `id_source`: Something unique, e.g. `"my_panel"`.
fn new(side: Side, id_source: impl std::hash::Hash) -> Self {
Self {
side,
id: Id::new(id_source),
frame: None,
resizable: true,
default_width: 200.0,
width_range: 96.0..=f32::INFINITY,
}
}
/// Switch resizable on/off.
/// Default is `true`.
pub fn resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
}
/// The initial wrapping width of the `SidePanel`.
pub fn default_width(mut self, default_width: f32) -> Self {
self.default_width = default_width;
self
}
pub fn min_width(mut self, min_width: f32) -> Self {
self.width_range = min_width..=(*self.width_range.end());
self
}
pub fn max_width(mut self, max_width: f32) -> Self {
self.width_range = (*self.width_range.start())..=max_width;
self
}
/// The allowable width range for resizable panels.
pub fn width_range(mut self, width_range: RangeInclusive<f32>) -> Self {
self.width_range = width_range;
self
}
/// Change the background color, margins, etc.
@ -55,16 +143,63 @@ impl SidePanel {
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let Self {
side,
id,
max_width,
frame,
resizable,
default_width,
width_range,
} = self;
let mut panel_rect = ctx.available_rect();
panel_rect.max.x = panel_rect.max.x.at_most(panel_rect.min.x + max_width);
let layer_id = LayerId::background();
let available_rect = ctx.available_rect();
let mut panel_rect = available_rect;
{
let mut width = default_width;
if let Some(state) = ctx.memory().id_data.get::<PanelState>(&id) {
width = state.rect.width();
}
width = clamp_to_range(width, width_range.clone()).at_most(available_rect.width());
side.set_rect_width(&mut panel_rect, width);
}
let mut resize_hover = false;
let mut is_resizing = false;
if resizable {
let resize_id = id.with("__resize");
if let Some(pointer) = ctx.input().pointer.latest_pos() {
let resize_x = side.opposite().side_x(panel_rect);
let mouse_over_resize_line = panel_rect.y_range().contains(&pointer.y)
&& (resize_x - pointer.x).abs()
<= ctx.style().interaction.resize_grab_radius_side;
if ctx.input().pointer.any_pressed()
&& ctx.input().pointer.any_down()
&& mouse_over_resize_line
{
ctx.memory().interaction.drag_id = Some(resize_id);
}
is_resizing = ctx.memory().interaction.drag_id == Some(resize_id);
if is_resizing {
let width = (pointer.x - side.side_x(panel_rect)).abs();
let width = clamp_to_range(width, width_range).at_most(available_rect.width());
side.set_rect_width(&mut panel_rect, width);
}
let we_are_on_top = ctx
.layer_id_at(pointer)
.map_or(true, |top_layer_id| top_layer_id == layer_id);
let dragging_something_else =
ctx.input().pointer.any_down() || ctx.input().pointer.any_pressed();
resize_hover = mouse_over_resize_line && !dragging_something_else && we_are_on_top;
if resize_hover || is_resizing {
ctx.output().cursor_icon = CursorIcon::ResizeHorizontal;
}
}
}
let clip_rect = ctx.input().screen_rect();
let mut panel_ui = Ui::new(ctx.clone(), layer_id, id, panel_rect, clip_rect);
@ -74,9 +209,32 @@ impl SidePanel {
add_contents(ui)
});
// Only inform ctx about what we actually used, so we can shrink the native window to fit.
ctx.frame_state()
.allocate_left_panel(inner_response.response.rect);
let rect = inner_response.response.rect;
ctx.memory().id_data.insert(id, PanelState { rect });
if resize_hover || is_resizing {
let stroke = if is_resizing {
ctx.style().visuals.widgets.active.bg_stroke
} else {
ctx.style().visuals.widgets.hovered.bg_stroke
};
// draw on top of ALL panels so that the resize line won't be covered by subsequent panels
let resize_layer = LayerId::new(Order::PanelResizeLine, Id::new("panel_resize"));
let resize_x = side.opposite().side_x(rect);
let top = pos2(resize_x, rect.top());
let bottom = pos2(resize_x, rect.bottom());
ctx.layer_painter(resize_layer)
.line_segment([top, bottom], stroke);
}
match side {
Side::Left => ctx
.frame_state()
.allocate_left_panel(Rect::from_min_max(available_rect.min, rect.max)),
Side::Right => ctx
.frame_state()
.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max)),
}
inner_response
}
@ -84,35 +242,114 @@ impl SidePanel {
// ----------------------------------------------------------------------------
/// A panel that covers the entire top side of the screen.
/// `Top` or `Bottom`
#[derive(Clone, Copy, Debug, PartialEq)]
enum TopBottomSide {
Top,
Bottom,
}
impl TopBottomSide {
fn opposite(self) -> Self {
match self {
TopBottomSide::Top => Self::Bottom,
TopBottomSide::Bottom => Self::Top,
}
}
fn set_rect_height(self, rect: &mut Rect, height: f32) {
match self {
TopBottomSide::Top => rect.max.y = rect.min.y + height,
TopBottomSide::Bottom => rect.min.y = rect.max.y - height,
}
}
fn side_y(self, rect: Rect) -> f32 {
match self {
TopBottomSide::Top => rect.top(),
TopBottomSide::Bottom => rect.bottom(),
}
}
}
/// A panel that covers the entire top or bottom of the screen.
///
/// `TopPanel`s must be added before adding any [`CentralPanel`] or [`Window`]s.
/// The order in which you add panels matter!
/// The first panel you add will always be the outermost, and the last you add will always be the innermost.
///
/// Always add any [`CentralPanel`] and [`Window`]:s last.
///
/// ```
/// # let mut ctx = egui::CtxRef::default();
/// # ctx.begin_frame(Default::default());
/// # let ctx = &ctx;
/// egui::TopPanel::top("my_top_panel").show(ctx, |ui| {
/// egui::TopBottomPanel::top("my_panel").show(ctx, |ui| {
/// ui.label("Hello World!");
/// });
/// ```
///
/// See also [`SidePanel`].
#[must_use = "You should call .show()"]
pub struct TopPanel {
pub struct TopBottomPanel {
side: TopBottomSide,
id: Id,
max_height: Option<f32>,
frame: Option<Frame>,
resizable: bool,
default_height: Option<f32>,
height_range: RangeInclusive<f32>,
}
impl TopPanel {
impl TopBottomPanel {
/// `id_source`: Something unique, e.g. `"my_top_panel"`.
/// Default height is that of `interact_size.y` (i.e. a button),
/// but the panel will expand as needed.
pub fn top(id_source: impl std::hash::Hash) -> Self {
Self {
id: Id::new(id_source),
max_height: None,
frame: None,
Self::new(TopBottomSide::Top, id_source)
}
/// `id_source`: Something unique, e.g. `"my_bottom_panel"`.
pub fn bottom(id_source: impl std::hash::Hash) -> Self {
Self::new(TopBottomSide::Bottom, id_source)
}
/// `id_source`: Something unique, e.g. `"my_panel"`.
fn new(side: TopBottomSide, id_source: impl std::hash::Hash) -> Self {
Self {
side,
id: Id::new(id_source),
frame: None,
resizable: false,
default_height: None,
height_range: 20.0..=f32::INFINITY,
}
}
/// Switch resizable on/off.
/// Default is `false`.
pub fn resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
}
/// The initial height of the `SidePanel`.
/// Defaults to [`style::Spacing::interact_size`].y.
pub fn default_height(mut self, default_height: f32) -> Self {
self.default_height = Some(default_height);
self
}
pub fn min_height(mut self, min_height: f32) -> Self {
self.height_range = min_height..=(*self.height_range.end());
self
}
pub fn max_height(mut self, max_height: f32) -> Self {
self.height_range = (*self.height_range.start())..=max_height;
self
}
/// The allowable height range for resizable panels.
pub fn height_range(mut self, height_range: RangeInclusive<f32>) -> Self {
self.height_range = height_range;
self
}
/// Change the background color, margins, etc.
@ -122,24 +359,73 @@ impl TopPanel {
}
}
impl TopPanel {
impl TopBottomPanel {
pub fn show<R>(
self,
ctx: &CtxRef,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let Self {
side,
id,
max_height,
frame,
resizable,
default_height,
height_range,
} = self;
let max_height = max_height.unwrap_or_else(|| ctx.style().spacing.interact_size.y);
let mut panel_rect = ctx.available_rect();
panel_rect.max.y = panel_rect.max.y.at_most(panel_rect.min.y + max_height);
let layer_id = LayerId::background();
let available_rect = ctx.available_rect();
let mut panel_rect = available_rect;
{
let state = ctx.memory().id_data.get::<PanelState>(&id).copied();
let mut height = if let Some(state) = state {
state.rect.height()
} else {
default_height.unwrap_or_else(|| ctx.style().spacing.interact_size.y)
};
height = clamp_to_range(height, height_range.clone()).at_most(available_rect.height());
side.set_rect_height(&mut panel_rect, height);
}
let mut resize_hover = false;
let mut is_resizing = false;
if resizable {
let resize_id = id.with("__resize");
if let Some(pointer) = ctx.input().pointer.latest_pos() {
let resize_y = side.opposite().side_y(panel_rect);
let mouse_over_resize_line = panel_rect.x_range().contains(&pointer.x)
&& (resize_y - pointer.y).abs()
<= ctx.style().interaction.resize_grab_radius_side;
if ctx.input().pointer.any_pressed()
&& ctx.input().pointer.any_down()
&& mouse_over_resize_line
{
ctx.memory().interaction.drag_id = Some(resize_id);
}
is_resizing = ctx.memory().interaction.drag_id == Some(resize_id);
if is_resizing {
let height = (pointer.y - side.side_y(panel_rect)).abs();
let height =
clamp_to_range(height, height_range).at_most(available_rect.height());
side.set_rect_height(&mut panel_rect, height);
}
let we_are_on_top = ctx
.layer_id_at(pointer)
.map_or(true, |top_layer_id| top_layer_id == layer_id);
let dragging_something_else =
ctx.input().pointer.any_down() || ctx.input().pointer.any_pressed();
resize_hover = mouse_over_resize_line && !dragging_something_else && we_are_on_top;
if resize_hover || is_resizing {
ctx.output().cursor_icon = CursorIcon::ResizeVertical;
}
}
}
let clip_rect = ctx.input().screen_rect();
let mut panel_ui = Ui::new(ctx.clone(), layer_id, id, panel_rect, clip_rect);
@ -149,9 +435,34 @@ impl TopPanel {
add_contents(ui)
});
// Only inform ctx about what we actually used, so we can shrink the native window to fit.
let rect = inner_response.response.rect;
ctx.memory().id_data.insert(id, PanelState { rect });
if resize_hover || is_resizing {
let stroke = if is_resizing {
ctx.style().visuals.widgets.active.bg_stroke
} else {
ctx.style().visuals.widgets.hovered.bg_stroke
};
// draw on top of ALL panels so that the resize line won't be covered by subsequent panels
let resize_layer = LayerId::new(Order::PanelResizeLine, Id::new("panel_resize"));
let resize_y = side.opposite().side_y(rect);
let left = pos2(rect.left(), resize_y);
let right = pos2(rect.right(), resize_y);
ctx.layer_painter(resize_layer)
.line_segment([left, right], stroke);
}
match side {
TopBottomSide::Top => {
ctx.frame_state()
.allocate_top_panel(inner_response.response.rect);
.allocate_top_panel(Rect::from_min_max(available_rect.min, rect.max));
}
TopBottomSide::Bottom => {
ctx.frame_state()
.allocate_bottom_panel(Rect::from_min_max(rect.min, available_rect.max));
}
}
inner_response
}
@ -159,6 +470,19 @@ impl TopPanel {
// ----------------------------------------------------------------------------
#[deprecated = "Use TopBottomPanel::top instead"]
pub struct TopPanel {}
#[allow(deprecated)]
impl TopPanel {
#[deprecated = "Use TopBottomPanel::top instead"]
pub fn top(id_source: impl std::hash::Hash) -> TopBottomPanel {
TopBottomPanel::top(id_source)
}
}
// ----------------------------------------------------------------------------
/// A panel that covers the remainder of the screen,
/// i.e. whatever area is left after adding other panels.
///
@ -216,3 +540,10 @@ impl CentralPanel {
inner_response
}
}
fn clamp_to_range(x: f32, range: RangeInclusive<f32>) -> f32 {
x.clamp(
range.start().min(*range.end()),
range.start().max(*range.end()),
)
}

View file

@ -97,7 +97,7 @@ impl CtxRef {
/// This will modify the internal reference to point to a new generation of [`Context`].
/// Any old clones of this [`CtxRef`] will refer to the old [`Context`], which will not get new input.
///
/// Put your widgets into a [`SidePanel`], [`TopPanel`], [`CentralPanel`], [`Window`] or [`Area`].
/// Put your widgets into a [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`].
pub fn begin_frame(&mut self, new_input: RawInput) {
let mut self_: Context = (*self.0).clone();
self_.begin_frame_mut(new_input);
@ -291,8 +291,14 @@ impl CtxRef {
response
}
/// Get a full-screen painter for a new or existing layer
pub fn layer_painter(&self, layer_id: LayerId) -> Painter {
Painter::new(self.clone(), layer_id, self.input.screen_rect())
}
/// Paint on top of everything else
pub fn debug_painter(&self) -> Painter {
Painter::new(self.clone(), LayerId::debug(), self.input.screen_rect())
Self::layer_painter(self, LayerId::debug())
}
}

View file

@ -88,6 +88,17 @@ impl FrameState {
self.used_by_panels = self.used_by_panels.union(panel_rect);
}
/// Shrink `available_rect`.
pub(crate) fn allocate_right_panel(&mut self, panel_rect: Rect) {
crate::egui_assert!(
panel_rect.max.distance(self.available_rect.max) < 0.1,
"Mismatching right panel. You must not create a panel from within another panel."
);
self.available_rect.max.x = panel_rect.min.x;
self.unused_rect.max.x = panel_rect.min.x;
self.used_by_panels = self.used_by_panels.union(panel_rect);
}
/// Shrink `available_rect`.
pub(crate) fn allocate_top_panel(&mut self, panel_rect: Rect) {
crate::egui_assert!(
@ -99,6 +110,17 @@ impl FrameState {
self.used_by_panels = self.used_by_panels.union(panel_rect);
}
/// Shrink `available_rect`.
pub(crate) fn allocate_bottom_panel(&mut self, panel_rect: Rect) {
crate::egui_assert!(
panel_rect.max.distance(self.available_rect.max) < 0.1,
"Mismatching bottom panel. You must not create a panel from within another panel."
);
self.available_rect.max.y = panel_rect.min.y;
self.unused_rect.max.y = panel_rect.min.y;
self.used_by_panels = self.used_by_panels.union(panel_rect);
}
pub(crate) fn allocate_central_panel(&mut self, panel_rect: Rect) {
// Note: we do not shrink `available_rect`, because
// we allow windows to cover the CentralPanel.

View file

@ -13,6 +13,8 @@ use std::sync::Arc;
pub enum Order {
/// Painted behind all floating windows
Background,
/// Special layer between panels and windows
PanelResizeLine,
/// Normal moveable windows that you reorder by click
Middle,
/// Popups, menus etc that should always be painted on top of windows
@ -25,9 +27,10 @@ pub enum Order {
Debug,
}
impl Order {
const COUNT: usize = 5;
const COUNT: usize = 6;
const ALL: [Order; Self::COUNT] = [
Self::Background,
Self::PanelResizeLine,
Self::Middle,
Self::Foreground,
Self::Tooltip,
@ -37,7 +40,11 @@ impl Order {
#[inline(always)]
pub fn allow_interaction(&self) -> bool {
match self {
Self::Background | Self::Middle | Self::Foreground | Self::Debug => true,
Self::Background
| Self::PanelResizeLine
| Self::Middle
| Self::Foreground
| Self::Debug => true,
Self::Tooltip => false,
}
}

View file

@ -41,7 +41,7 @@
//!
//! ### Getting a [`Ui`]
//!
//! Use one of [`SidePanel`], [`TopPanel`], [`CentralPanel`], [`Window`] or [`Area`] to
//! Use one of [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`] to
//! get access to an [`Ui`] where you can put widgets. For example:
//!
//! ```

View file

@ -36,7 +36,7 @@ impl BarState {
}
}
/// The menu bar goes well in `TopPanel`,
/// The menu bar goes well in a [`TopBottomPanel::top`],
/// but can also be placed in a `Window`.
/// In the latter case you may want to wrap it in `Frame`.
pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {

View file

@ -63,7 +63,7 @@ impl Ui {
/// Create a new `Ui`.
///
/// Normally you would not use this directly, but instead use
/// [`SidePanel`], [`TopPanel`], [`CentralPanel`], [`Window`] or [`Area`].
/// [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`].
pub fn new(ctx: CtxRef, layer_id: LayerId, id: Id, max_rect: Rect, clip_rect: Rect) -> Self {
let style = ctx.style();
Ui {

View file

@ -152,7 +152,10 @@ impl DemoWindows {
egui_windows,
} = self;
egui::SidePanel::left("side_panel", 190.0).show(ctx, |ui| {
egui::SidePanel::right("egui_demo_panel")
.min_width(150.0)
.default_width(190.0)
.show(ctx, |ui| {
ui.vertical_centered(|ui| {
ui.heading("✒ egui demos");
});
@ -192,7 +195,7 @@ impl DemoWindows {
});
});
egui::TopPanel::top("menu_bar").show(ctx, |ui| {
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
show_menu_bar(ui);
});
@ -302,7 +305,7 @@ fn show_menu_bar(ui: &mut Ui) {
}
if ui
.button("Clear egui memory")
.on_hover_text("Forget scroll, collapsing headers etc")
.on_hover_text("Forget scroll, positions, sizes etc")
.clicked()
{
*ui.ctx().memory() = Default::default();

View file

@ -61,7 +61,7 @@ impl epi::App for HttpApp {
}
/// Called each time the UI needs repainting, which may be many times per second.
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
/// Put your widgets into a `SidePanel`, `TopBottomPanel`, `CentralPanel`, `Window` or `Area`.
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
if let Some(receiver) = &mut self.in_progress {
// Are we there yet?

View file

@ -74,7 +74,30 @@ impl epi::App for WrapApp {
self.selected_anchor = self.apps.iter_mut().next().unwrap().0.to_owned();
}
egui::TopPanel::top("wrap_app_top_bar").show(ctx, |ui| {
egui::TopBottomPanel::top("wrap_app_top_bar").show(ctx, |ui| {
self.bar_contents(ui, frame);
});
self.backend_panel.update(ctx, frame);
if self.backend_panel.open || ctx.memory().everything_is_visible() {
egui::SidePanel::left("backend_panel").show(ctx, |ui| {
self.backend_panel.ui(ui, frame);
});
}
for (anchor, app) in self.apps.iter_mut() {
if anchor == self.selected_anchor || ctx.memory().everything_is_visible() {
app.update(ctx, frame);
}
}
self.backend_panel.end_of_frame(ctx);
}
}
impl WrapApp {
fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
// A menu-bar is a horizontal layout with some special styles applied.
// egui::menu::bar(ui, |ui| {
ui.horizontal_wrapped(|ui| {
@ -111,23 +134,6 @@ impl epi::App for WrapApp {
egui::warn_if_debug_build(ui);
});
});
});
self.backend_panel.update(ctx, frame);
if self.backend_panel.open || ctx.memory().everything_is_visible() {
egui::SidePanel::left("backend_panel", 150.0).show(ctx, |ui| {
self.backend_panel.ui(ui, frame);
});
}
for (anchor, app) in self.apps.iter_mut() {
if anchor == self.selected_anchor || ctx.memory().everything_is_visible() {
app.update(ctx, frame);
}
}
self.backend_panel.end_of_frame(ctx);
}
}
@ -256,10 +262,21 @@ impl BackendPanel {
}
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
ui.vertical_centered(|ui| {
ui.heading("💻 Backend");
});
ui.separator();
self.run_mode_ui(ui);
if ui
.button("Clear egui memory")
.on_hover_text("Forget scroll, positions, sizes etc")
.clicked()
{
*ui.ctx().memory() = Default::default();
}
ui.separator();
self.frame_history.ui(ui);
@ -274,6 +291,15 @@ impl BackendPanel {
}
}
if !frame.is_web()
&& ui
.button("📱 Phone Size")
.on_hover_text("Resize the window to be small like a phone.")
.clicked()
{
frame.set_window_size(egui::Vec2::new(375.0, 812.0)); // iPhone 12 mini
}
ui.separator();
if frame.is_web() {
@ -299,17 +325,6 @@ impl BackendPanel {
if !ui.ctx().is_using_pointer() {
self.max_size_points_active = self.max_size_points_ui;
}
} else {
if ui
.button("📱 Phone Size")
.on_hover_text("Resize the window to be small like a phone.")
.clicked()
{
frame.set_window_size(egui::Vec2::new(375.0, 812.0)); // iPhone 12 mini
}
if ui.button("Quit").clicked() {
frame.quit();
}
}
let mut screen_reader = ui.ctx().memory().options.screen_reader;
@ -328,6 +343,13 @@ impl BackendPanel {
ui.label(format!("{:?}", event));
}
});
if !frame.is_web() {
ui.separator();
if ui.button("Quit").clicked() {
frame.quit();
}
}
}
fn pixels_per_point_ui(
@ -347,6 +369,7 @@ impl BackendPanel {
ui.add(
egui::Slider::new(pixels_per_point, 0.5..=5.0)
.logarithmic(true)
.clamp_to_range(true)
.text("Scale"),
)
.on_hover_text("Physical pixels per point.");

View file

@ -31,7 +31,7 @@ fn main() {
let mut quit = false;
egui::SidePanel::left("my_side_panel", 300.0).show(egui.ctx(), |ui| {
egui::SidePanel::left("my_side_panel").show(egui.ctx(), |ui| {
ui.heading("Hello World!");
if ui.button("Quit").clicked() {
quit = true;

View file

@ -202,6 +202,11 @@ impl Rect {
self.min.x <= p.x && p.x <= self.max.x && self.min.y <= p.y && p.y <= self.max.y
}
#[must_use]
pub fn contains_rect(&self, other: Rect) -> bool {
self.contains(other.min) && self.contains(other.max)
}
/// Return the given points clamped to be inside the rectangle
/// Panics if [`Self::is_negative`].
#[must_use]

View file

@ -91,7 +91,7 @@ pub use egui; // Re-export for user convenience
/// and deployed as a web site using the [`egui_web`](https://crates.io/crates/egui_web) crate.
pub trait App {
/// Called each time the UI needs repainting, which may be many times per second.
/// Put your widgets into a [`egui::SidePanel`], [`egui::TopPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
/// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut Frame<'_>);
/// Called once before the first frame.