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:
parent
196ddff499
commit
c9766f8a7b
16 changed files with 538 additions and 131 deletions
|
@ -8,13 +8,20 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Added ⭐
|
### 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).
|
* 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`.
|
* You can now change `TextStyle` on checkboxes, radio buttons and `SelectableLabel`.
|
||||||
* Add support for [cint](https://crates.io/crates/cint) under `cint` feature.
|
* 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 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`.
|
* `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
|
## 0.12.0 - 2021-05-10 - Multitouch, user memory, window pivots, and improved plots
|
||||||
|
|
||||||
### Added ⭐
|
### Added ⭐
|
||||||
|
|
|
@ -282,7 +282,7 @@ impl Prepared {
|
||||||
// (except in rare cases where they don't fit).
|
// (except in rare cases where they don't fit).
|
||||||
// Adjust clip rect so we don't cast shadows on side panels:
|
// Adjust clip rect so we don't cast shadows on side panels:
|
||||||
let central_area = ctx.available_rect();
|
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 {
|
if is_within_central_area {
|
||||||
clip_rect = clip_rect.intersect(central_area);
|
clip_rect = clip_rect.intersect(central_area);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,12 @@ pub use {
|
||||||
collapsing_header::*,
|
collapsing_header::*,
|
||||||
combo_box::*,
|
combo_box::*,
|
||||||
frame::Frame,
|
frame::Frame,
|
||||||
panel::{CentralPanel, SidePanel, TopPanel},
|
panel::{CentralPanel, SidePanel, TopBottomPanel},
|
||||||
popup::*,
|
popup::*,
|
||||||
resize::Resize,
|
resize::Resize,
|
||||||
scroll_area::ScrollArea,
|
scroll_area::ScrollArea,
|
||||||
window::Window,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
pub use panel::TopPanel;
|
||||||
|
|
|
@ -4,43 +4,131 @@
|
||||||
//! the only places where you can put you widgets.
|
//! the only places where you can put you widgets.
|
||||||
//!
|
//!
|
||||||
//! The order in which you add panels matter!
|
//! 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::*;
|
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();
|
/// # let mut ctx = egui::CtxRef::default();
|
||||||
/// # ctx.begin_frame(Default::default());
|
/// # ctx.begin_frame(Default::default());
|
||||||
/// # let ctx = &ctx;
|
/// # 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!");
|
/// ui.label("Hello World!");
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// See also [`TopBottomPanel`].
|
||||||
#[must_use = "You should call .show()"]
|
#[must_use = "You should call .show()"]
|
||||||
pub struct SidePanel {
|
pub struct SidePanel {
|
||||||
|
side: Side,
|
||||||
id: Id,
|
id: Id,
|
||||||
max_width: f32,
|
|
||||||
frame: Option<Frame>,
|
frame: Option<Frame>,
|
||||||
|
resizable: bool,
|
||||||
|
default_width: f32,
|
||||||
|
width_range: RangeInclusive<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SidePanel {
|
impl SidePanel {
|
||||||
/// `id_source`: Something unique, e.g. `"my_side_panel"`.
|
/// `id_source`: Something unique, e.g. `"my_left_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) -> Self {
|
||||||
pub fn left(id_source: impl std::hash::Hash, max_width: f32) -> 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 {
|
Self {
|
||||||
|
side,
|
||||||
id: Id::new(id_source),
|
id: Id::new(id_source),
|
||||||
max_width,
|
|
||||||
frame: None,
|
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.
|
/// Change the background color, margins, etc.
|
||||||
pub fn frame(mut self, frame: Frame) -> Self {
|
pub fn frame(mut self, frame: Frame) -> Self {
|
||||||
self.frame = Some(frame);
|
self.frame = Some(frame);
|
||||||
|
@ -55,16 +143,63 @@ impl SidePanel {
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
let Self {
|
let Self {
|
||||||
|
side,
|
||||||
id,
|
id,
|
||||||
max_width,
|
|
||||||
frame,
|
frame,
|
||||||
|
resizable,
|
||||||
|
default_width,
|
||||||
|
width_range,
|
||||||
} = self;
|
} = 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 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 clip_rect = ctx.input().screen_rect();
|
||||||
let mut panel_ui = Ui::new(ctx.clone(), layer_id, id, panel_rect, clip_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)
|
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.frame_state()
|
ctx.memory().id_data.insert(id, PanelState { rect });
|
||||||
.allocate_left_panel(inner_response.response.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
|
inner_response
|
||||||
}
|
}
|
||||||
|
@ -84,37 +242,116 @@ 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();
|
/// # let mut ctx = egui::CtxRef::default();
|
||||||
/// # ctx.begin_frame(Default::default());
|
/// # ctx.begin_frame(Default::default());
|
||||||
/// # let ctx = &ctx;
|
/// # let ctx = &ctx;
|
||||||
/// egui::TopPanel::top("my_top_panel").show(ctx, |ui| {
|
/// egui::TopBottomPanel::top("my_panel").show(ctx, |ui| {
|
||||||
/// ui.label("Hello World!");
|
/// ui.label("Hello World!");
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// See also [`SidePanel`].
|
||||||
#[must_use = "You should call .show()"]
|
#[must_use = "You should call .show()"]
|
||||||
pub struct TopPanel {
|
pub struct TopBottomPanel {
|
||||||
|
side: TopBottomSide,
|
||||||
id: Id,
|
id: Id,
|
||||||
max_height: Option<f32>,
|
|
||||||
frame: Option<Frame>,
|
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"`.
|
/// `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 {
|
pub fn top(id_source: impl std::hash::Hash) -> Self {
|
||||||
|
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 {
|
Self {
|
||||||
|
side,
|
||||||
id: Id::new(id_source),
|
id: Id::new(id_source),
|
||||||
max_height: None,
|
|
||||||
frame: None,
|
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.
|
/// Change the background color, margins, etc.
|
||||||
pub fn frame(mut self, frame: Frame) -> Self {
|
pub fn frame(mut self, frame: Frame) -> Self {
|
||||||
self.frame = Some(frame);
|
self.frame = Some(frame);
|
||||||
|
@ -122,24 +359,73 @@ impl TopPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TopPanel {
|
impl TopBottomPanel {
|
||||||
pub fn show<R>(
|
pub fn show<R>(
|
||||||
self,
|
self,
|
||||||
ctx: &CtxRef,
|
ctx: &CtxRef,
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
let Self {
|
let Self {
|
||||||
|
side,
|
||||||
id,
|
id,
|
||||||
max_height,
|
|
||||||
frame,
|
frame,
|
||||||
|
resizable,
|
||||||
|
default_height,
|
||||||
|
height_range,
|
||||||
} = self;
|
} = 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 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 clip_rect = ctx.input().screen_rect();
|
||||||
let mut panel_ui = Ui::new(ctx.clone(), layer_id, id, panel_rect, clip_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)
|
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.frame_state()
|
ctx.memory().id_data.insert(id, PanelState { rect });
|
||||||
.allocate_top_panel(inner_response.response.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(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
|
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,
|
/// A panel that covers the remainder of the screen,
|
||||||
/// i.e. whatever area is left after adding other panels.
|
/// i.e. whatever area is left after adding other panels.
|
||||||
///
|
///
|
||||||
|
@ -216,3 +540,10 @@ impl CentralPanel {
|
||||||
inner_response
|
inner_response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clamp_to_range(x: f32, range: RangeInclusive<f32>) -> f32 {
|
||||||
|
x.clamp(
|
||||||
|
range.start().min(*range.end()),
|
||||||
|
range.start().max(*range.end()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ impl CtxRef {
|
||||||
/// This will modify the internal reference to point to a new generation of [`Context`].
|
/// 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.
|
/// 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) {
|
pub fn begin_frame(&mut self, new_input: RawInput) {
|
||||||
let mut self_: Context = (*self.0).clone();
|
let mut self_: Context = (*self.0).clone();
|
||||||
self_.begin_frame_mut(new_input);
|
self_.begin_frame_mut(new_input);
|
||||||
|
@ -291,8 +291,14 @@ impl CtxRef {
|
||||||
response
|
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 {
|
pub fn debug_painter(&self) -> Painter {
|
||||||
Painter::new(self.clone(), LayerId::debug(), self.input.screen_rect())
|
Self::layer_painter(self, LayerId::debug())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,17 @@ impl FrameState {
|
||||||
self.used_by_panels = self.used_by_panels.union(panel_rect);
|
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`.
|
/// Shrink `available_rect`.
|
||||||
pub(crate) fn allocate_top_panel(&mut self, panel_rect: Rect) {
|
pub(crate) fn allocate_top_panel(&mut self, panel_rect: Rect) {
|
||||||
crate::egui_assert!(
|
crate::egui_assert!(
|
||||||
|
@ -99,6 +110,17 @@ impl FrameState {
|
||||||
self.used_by_panels = self.used_by_panels.union(panel_rect);
|
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) {
|
pub(crate) fn allocate_central_panel(&mut self, panel_rect: Rect) {
|
||||||
// Note: we do not shrink `available_rect`, because
|
// Note: we do not shrink `available_rect`, because
|
||||||
// we allow windows to cover the CentralPanel.
|
// we allow windows to cover the CentralPanel.
|
||||||
|
|
|
@ -13,6 +13,8 @@ use std::sync::Arc;
|
||||||
pub enum Order {
|
pub enum Order {
|
||||||
/// Painted behind all floating windows
|
/// Painted behind all floating windows
|
||||||
Background,
|
Background,
|
||||||
|
/// Special layer between panels and windows
|
||||||
|
PanelResizeLine,
|
||||||
/// Normal moveable windows that you reorder by click
|
/// Normal moveable windows that you reorder by click
|
||||||
Middle,
|
Middle,
|
||||||
/// Popups, menus etc that should always be painted on top of windows
|
/// Popups, menus etc that should always be painted on top of windows
|
||||||
|
@ -25,9 +27,10 @@ pub enum Order {
|
||||||
Debug,
|
Debug,
|
||||||
}
|
}
|
||||||
impl Order {
|
impl Order {
|
||||||
const COUNT: usize = 5;
|
const COUNT: usize = 6;
|
||||||
const ALL: [Order; Self::COUNT] = [
|
const ALL: [Order; Self::COUNT] = [
|
||||||
Self::Background,
|
Self::Background,
|
||||||
|
Self::PanelResizeLine,
|
||||||
Self::Middle,
|
Self::Middle,
|
||||||
Self::Foreground,
|
Self::Foreground,
|
||||||
Self::Tooltip,
|
Self::Tooltip,
|
||||||
|
@ -37,7 +40,11 @@ impl Order {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_interaction(&self) -> bool {
|
pub fn allow_interaction(&self) -> bool {
|
||||||
match self {
|
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,
|
Self::Tooltip => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
//!
|
//!
|
||||||
//! ### Getting a [`Ui`]
|
//! ### 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:
|
//! get access to an [`Ui`] where you can put widgets. For example:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
|
|
|
@ -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`.
|
/// but can also be placed in a `Window`.
|
||||||
/// In the latter case you may want to wrap it in `Frame`.
|
/// 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> {
|
pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
|
||||||
|
|
|
@ -63,7 +63,7 @@ impl Ui {
|
||||||
/// Create a new `Ui`.
|
/// Create a new `Ui`.
|
||||||
///
|
///
|
||||||
/// Normally you would not use this directly, but instead use
|
/// 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 {
|
pub fn new(ctx: CtxRef, layer_id: LayerId, id: Id, max_rect: Rect, clip_rect: Rect) -> Self {
|
||||||
let style = ctx.style();
|
let style = ctx.style();
|
||||||
Ui {
|
Ui {
|
||||||
|
|
|
@ -152,47 +152,50 @@ impl DemoWindows {
|
||||||
egui_windows,
|
egui_windows,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
egui::SidePanel::left("side_panel", 190.0).show(ctx, |ui| {
|
egui::SidePanel::right("egui_demo_panel")
|
||||||
ui.vertical_centered(|ui| {
|
.min_width(150.0)
|
||||||
ui.heading("✒ egui demos");
|
.default_width(190.0)
|
||||||
});
|
.show(ctx, |ui| {
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
ScrollArea::auto_sized().show(ui, |ui| {
|
|
||||||
use egui::special_emojis::{GITHUB, OS_APPLE, OS_LINUX, OS_WINDOWS};
|
|
||||||
|
|
||||||
ui.label("egui is an immediate mode GUI library written in Rust.");
|
|
||||||
|
|
||||||
ui.label(format!(
|
|
||||||
"egui runs on the web, or natively on {}{}{}",
|
|
||||||
OS_APPLE, OS_LINUX, OS_WINDOWS,
|
|
||||||
));
|
|
||||||
|
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.hyperlink_to(
|
ui.heading("✒ egui demos");
|
||||||
format!("{} egui home page", GITHUB),
|
|
||||||
"https://github.com/emilk/egui",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
demos.checkboxes(ui);
|
|
||||||
ui.separator();
|
|
||||||
tests.checkboxes(ui);
|
|
||||||
ui.separator();
|
|
||||||
egui_windows.checkboxes(ui);
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.vertical_centered(|ui| {
|
ScrollArea::auto_sized().show(ui, |ui| {
|
||||||
if ui.button("Organize windows").clicked() {
|
use egui::special_emojis::{GITHUB, OS_APPLE, OS_LINUX, OS_WINDOWS};
|
||||||
ui.ctx().memory().reset_areas();
|
|
||||||
}
|
ui.label("egui is an immediate mode GUI library written in Rust.");
|
||||||
|
|
||||||
|
ui.label(format!(
|
||||||
|
"egui runs on the web, or natively on {}{}{}",
|
||||||
|
OS_APPLE, OS_LINUX, OS_WINDOWS,
|
||||||
|
));
|
||||||
|
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
ui.hyperlink_to(
|
||||||
|
format!("{} egui home page", GITHUB),
|
||||||
|
"https://github.com/emilk/egui",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
demos.checkboxes(ui);
|
||||||
|
ui.separator();
|
||||||
|
tests.checkboxes(ui);
|
||||||
|
ui.separator();
|
||||||
|
egui_windows.checkboxes(ui);
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
if ui.button("Organize windows").clicked() {
|
||||||
|
ui.ctx().memory().reset_areas();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
egui::TopPanel::top("menu_bar").show(ctx, |ui| {
|
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||||
show_menu_bar(ui);
|
show_menu_bar(ui);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -302,7 +305,7 @@ fn show_menu_bar(ui: &mut Ui) {
|
||||||
}
|
}
|
||||||
if ui
|
if ui
|
||||||
.button("Clear egui memory")
|
.button("Clear egui memory")
|
||||||
.on_hover_text("Forget scroll, collapsing headers etc")
|
.on_hover_text("Forget scroll, positions, sizes etc")
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
*ui.ctx().memory() = Default::default();
|
*ui.ctx().memory() = Default::default();
|
||||||
|
|
|
@ -61,7 +61,7 @@ impl epi::App for HttpApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called each time the UI needs repainting, which may be many times per second.
|
/// 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<'_>) {
|
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||||
if let Some(receiver) = &mut self.in_progress {
|
if let Some(receiver) = &mut self.in_progress {
|
||||||
// Are we there yet?
|
// Are we there yet?
|
||||||
|
|
|
@ -74,49 +74,14 @@ impl epi::App for WrapApp {
|
||||||
self.selected_anchor = self.apps.iter_mut().next().unwrap().0.to_owned();
|
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| {
|
||||||
// A menu-bar is a horizontal layout with some special styles applied.
|
self.bar_contents(ui, frame);
|
||||||
// egui::menu::bar(ui, |ui| {
|
|
||||||
ui.horizontal_wrapped(|ui| {
|
|
||||||
dark_light_mode_switch(ui);
|
|
||||||
|
|
||||||
ui.checkbox(&mut self.backend_panel.open, "💻 Backend");
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
for (anchor, app) in self.apps.iter_mut() {
|
|
||||||
if ui
|
|
||||||
.selectable_label(self.selected_anchor == anchor, app.name())
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
self.selected_anchor = anchor.to_owned();
|
|
||||||
if frame.is_web() {
|
|
||||||
ui.output().open_url(format!("#{}", anchor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.with_layout(egui::Layout::right_to_left(), |ui| {
|
|
||||||
if false {
|
|
||||||
// TODO: fix the overlap on small screens
|
|
||||||
if let Some(seconds_since_midnight) = frame.info().seconds_since_midnight {
|
|
||||||
if clock_button(ui, seconds_since_midnight).clicked() {
|
|
||||||
self.selected_anchor = "clock".to_owned();
|
|
||||||
if frame.is_web() {
|
|
||||||
ui.output().open_url("#clock");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
egui::warn_if_debug_build(ui);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
self.backend_panel.update(ctx, frame);
|
self.backend_panel.update(ctx, frame);
|
||||||
|
|
||||||
if self.backend_panel.open || ctx.memory().everything_is_visible() {
|
if self.backend_panel.open || ctx.memory().everything_is_visible() {
|
||||||
egui::SidePanel::left("backend_panel", 150.0).show(ctx, |ui| {
|
egui::SidePanel::left("backend_panel").show(ctx, |ui| {
|
||||||
self.backend_panel.ui(ui, frame);
|
self.backend_panel.ui(ui, frame);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -131,6 +96,47 @@ impl epi::App for WrapApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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| {
|
||||||
|
dark_light_mode_switch(ui);
|
||||||
|
|
||||||
|
ui.checkbox(&mut self.backend_panel.open, "💻 Backend");
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
for (anchor, app) in self.apps.iter_mut() {
|
||||||
|
if ui
|
||||||
|
.selectable_label(self.selected_anchor == anchor, app.name())
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.selected_anchor = anchor.to_owned();
|
||||||
|
if frame.is_web() {
|
||||||
|
ui.output().open_url(format!("#{}", anchor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.with_layout(egui::Layout::right_to_left(), |ui| {
|
||||||
|
if false {
|
||||||
|
// TODO: fix the overlap on small screens
|
||||||
|
if let Some(seconds_since_midnight) = frame.info().seconds_since_midnight {
|
||||||
|
if clock_button(ui, seconds_since_midnight).clicked() {
|
||||||
|
self.selected_anchor = "clock".to_owned();
|
||||||
|
if frame.is_web() {
|
||||||
|
ui.output().open_url("#clock");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
egui::warn_if_debug_build(ui);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn clock_button(ui: &mut egui::Ui, seconds_since_midnight: f64) -> egui::Response {
|
fn clock_button(ui: &mut egui::Ui, seconds_since_midnight: f64) -> egui::Response {
|
||||||
let time = seconds_since_midnight;
|
let time = seconds_since_midnight;
|
||||||
let time = format!(
|
let time = format!(
|
||||||
|
@ -256,10 +262,21 @@ impl BackendPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
|
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
|
||||||
ui.heading("💻 Backend");
|
ui.vertical_centered(|ui| {
|
||||||
|
ui.heading("💻 Backend");
|
||||||
|
});
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
self.run_mode_ui(ui);
|
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();
|
ui.separator();
|
||||||
|
|
||||||
self.frame_history.ui(ui);
|
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();
|
ui.separator();
|
||||||
|
|
||||||
if frame.is_web() {
|
if frame.is_web() {
|
||||||
|
@ -299,17 +325,6 @@ impl BackendPanel {
|
||||||
if !ui.ctx().is_using_pointer() {
|
if !ui.ctx().is_using_pointer() {
|
||||||
self.max_size_points_active = self.max_size_points_ui;
|
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;
|
let mut screen_reader = ui.ctx().memory().options.screen_reader;
|
||||||
|
@ -328,6 +343,13 @@ impl BackendPanel {
|
||||||
ui.label(format!("{:?}", event));
|
ui.label(format!("{:?}", event));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if !frame.is_web() {
|
||||||
|
ui.separator();
|
||||||
|
if ui.button("Quit").clicked() {
|
||||||
|
frame.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pixels_per_point_ui(
|
fn pixels_per_point_ui(
|
||||||
|
@ -347,6 +369,7 @@ impl BackendPanel {
|
||||||
ui.add(
|
ui.add(
|
||||||
egui::Slider::new(pixels_per_point, 0.5..=5.0)
|
egui::Slider::new(pixels_per_point, 0.5..=5.0)
|
||||||
.logarithmic(true)
|
.logarithmic(true)
|
||||||
|
.clamp_to_range(true)
|
||||||
.text("Scale"),
|
.text("Scale"),
|
||||||
)
|
)
|
||||||
.on_hover_text("Physical pixels per point.");
|
.on_hover_text("Physical pixels per point.");
|
||||||
|
|
|
@ -31,7 +31,7 @@ fn main() {
|
||||||
|
|
||||||
let mut quit = false;
|
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!");
|
ui.heading("Hello World!");
|
||||||
if ui.button("Quit").clicked() {
|
if ui.button("Quit").clicked() {
|
||||||
quit = true;
|
quit = true;
|
||||||
|
|
|
@ -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
|
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
|
/// Return the given points clamped to be inside the rectangle
|
||||||
/// Panics if [`Self::is_negative`].
|
/// Panics if [`Self::is_negative`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
|
@ -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.
|
/// and deployed as a web site using the [`egui_web`](https://crates.io/crates/egui_web) crate.
|
||||||
pub trait App {
|
pub trait App {
|
||||||
/// Called each time the UI needs repainting, which may be many times per second.
|
/// 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<'_>);
|
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut Frame<'_>);
|
||||||
|
|
||||||
/// Called once before the first frame.
|
/// Called once before the first frame.
|
||||||
|
|
Loading…
Reference in a new issue