Panel collapse/expansion animation (#2190)
* Add API for querying the size of a panel * demo app: animate backend panel collapse * Add helper function for animating panels * More animation functions * Add line to changelog
This commit is contained in:
parent
da96fcacd3
commit
f7a15a34f9
4 changed files with 332 additions and 35 deletions
|
@ -5,10 +5,13 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
* ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)).
|
|
||||||
* ⚠️ BREAKING: egui now expects integrations to do all color blending in gamma space ([#2071](https://github.com/emilk/egui/pull/2071)).
|
* ⚠️ BREAKING: egui now expects integrations to do all color blending in gamma space ([#2071](https://github.com/emilk/egui/pull/2071)).
|
||||||
|
|
||||||
|
### Added ⭐
|
||||||
|
* Added helper functions for animating panels that collapse/expand ([#2190](https://github.com/emilk/egui/pull/2190)).
|
||||||
|
|
||||||
### Fixed 🐛
|
### Fixed 🐛
|
||||||
|
* ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)).
|
||||||
* Improved text rendering ([#2071](https://github.com/emilk/egui/pull/2071)).
|
* Improved text rendering ([#2071](https://github.com/emilk/egui/pull/2071)).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,17 +19,23 @@ use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
/// State regarding panels.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
struct PanelState {
|
pub struct PanelState {
|
||||||
rect: Rect,
|
pub rect: Rect,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PanelState {
|
impl PanelState {
|
||||||
fn load(ctx: &Context, bar_id: Id) -> Option<Self> {
|
pub fn load(ctx: &Context, bar_id: Id) -> Option<Self> {
|
||||||
ctx.data().get_persisted(bar_id)
|
ctx.data().get_persisted(bar_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The size of the panel (from previous frame).
|
||||||
|
pub fn size(&self) -> Vec2 {
|
||||||
|
self.rect.size()
|
||||||
|
}
|
||||||
|
|
||||||
fn store(self, ctx: &Context, bar_id: Id) {
|
fn store(self, ctx: &Context, bar_id: Id) {
|
||||||
ctx.data().insert_persisted(bar_id, self);
|
ctx.data().insert_persisted(bar_id, self);
|
||||||
}
|
}
|
||||||
|
@ -96,21 +102,21 @@ pub struct SidePanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SidePanel {
|
impl SidePanel {
|
||||||
/// `id_source`: Something unique, e.g. `"my_left_panel"`.
|
/// The id should be globally unique, e.g. `Id::new("my_left_panel")`.
|
||||||
pub fn left(id_source: impl std::hash::Hash) -> Self {
|
pub fn left(id: impl Into<Id>) -> Self {
|
||||||
Self::new(Side::Left, id_source)
|
Self::new(Side::Left, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `id_source`: Something unique, e.g. `"my_right_panel"`.
|
/// The id should be globally unique, e.g. `Id::new("my_right_panel")`.
|
||||||
pub fn right(id_source: impl std::hash::Hash) -> Self {
|
pub fn right(id: impl Into<Id>) -> Self {
|
||||||
Self::new(Side::Right, id_source)
|
Self::new(Side::Right, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `id_source`: Something unique, e.g. `"my_panel"`.
|
/// The id should be globally unique, e.g. `Id::new("my_panel")`.
|
||||||
pub fn new(side: Side, id_source: impl std::hash::Hash) -> Self {
|
pub fn new(side: Side, id: impl Into<Id>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
side,
|
side,
|
||||||
id: Id::new(id_source),
|
id: id.into(),
|
||||||
frame: None,
|
frame: None,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
default_width: 200.0,
|
default_width: 200.0,
|
||||||
|
@ -327,6 +333,135 @@ impl SidePanel {
|
||||||
}
|
}
|
||||||
inner_response
|
inner_response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show the panel if `is_expanded` is `true`,
|
||||||
|
/// otherwise don't show it, but with a nice animation between collapsed and expanded.
|
||||||
|
pub fn show_animated<R>(
|
||||||
|
self,
|
||||||
|
ctx: &Context,
|
||||||
|
is_expanded: bool,
|
||||||
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
|
) -> Option<InnerResponse<R>> {
|
||||||
|
let how_expanded = ctx.animate_bool(self.id.with("animation"), is_expanded);
|
||||||
|
|
||||||
|
if 0.0 == how_expanded {
|
||||||
|
None
|
||||||
|
} else if how_expanded < 1.0 {
|
||||||
|
// Show a fake panel in this in-between animation state:
|
||||||
|
let expanded_width = PanelState::load(ctx, self.id)
|
||||||
|
.map_or(self.default_width, |state| state.rect.width());
|
||||||
|
let fake_width = how_expanded * expanded_width;
|
||||||
|
Self {
|
||||||
|
id: self.id.with("animating_panel"),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
.resizable(false)
|
||||||
|
.exact_width(fake_width)
|
||||||
|
.show(ctx, |_ui| {});
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// Show the real panel:
|
||||||
|
Some(self.show(ctx, add_contents))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show the panel if `is_expanded` is `true`,
|
||||||
|
/// otherwise don't show it, but with a nice animation between collapsed and expanded.
|
||||||
|
pub fn show_animated_inside<R>(
|
||||||
|
self,
|
||||||
|
ui: &mut Ui,
|
||||||
|
is_expanded: bool,
|
||||||
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
|
) -> Option<InnerResponse<R>> {
|
||||||
|
let how_expanded = ui
|
||||||
|
.ctx()
|
||||||
|
.animate_bool(self.id.with("animation"), is_expanded);
|
||||||
|
|
||||||
|
if 0.0 == how_expanded {
|
||||||
|
None
|
||||||
|
} else if how_expanded < 1.0 {
|
||||||
|
// Show a fake panel in this in-between animation state:
|
||||||
|
let expanded_width = PanelState::load(ui.ctx(), self.id)
|
||||||
|
.map_or(self.default_width, |state| state.rect.width());
|
||||||
|
let fake_width = how_expanded * expanded_width;
|
||||||
|
Self {
|
||||||
|
id: self.id.with("animating_panel"),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
.resizable(false)
|
||||||
|
.exact_width(fake_width)
|
||||||
|
.show_inside(ui, |_ui| {});
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// Show the real panel:
|
||||||
|
Some(self.show_inside(ui, add_contents))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show either a collapsed or a expanded panel, with a nice animation between.
|
||||||
|
pub fn show_animated_between<R>(
|
||||||
|
ctx: &Context,
|
||||||
|
is_expanded: bool,
|
||||||
|
collapsed_panel: Self,
|
||||||
|
expanded_panel: Self,
|
||||||
|
add_contents: impl FnOnce(&mut Ui, f32) -> R,
|
||||||
|
) -> Option<InnerResponse<R>> {
|
||||||
|
let how_expanded = ctx.animate_bool(expanded_panel.id.with("animation"), is_expanded);
|
||||||
|
|
||||||
|
if 0.0 == how_expanded {
|
||||||
|
Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
|
||||||
|
} else if how_expanded < 1.0 {
|
||||||
|
// Show animation:
|
||||||
|
let collapsed_width = PanelState::load(ctx, collapsed_panel.id)
|
||||||
|
.map_or(collapsed_panel.default_width, |state| state.rect.width());
|
||||||
|
let expanded_width = PanelState::load(ctx, expanded_panel.id)
|
||||||
|
.map_or(expanded_panel.default_width, |state| state.rect.width());
|
||||||
|
let fake_width = lerp(collapsed_width..=expanded_width, how_expanded);
|
||||||
|
Self {
|
||||||
|
id: expanded_panel.id.with("animating_panel"),
|
||||||
|
..expanded_panel
|
||||||
|
}
|
||||||
|
.resizable(false)
|
||||||
|
.exact_width(fake_width)
|
||||||
|
.show(ctx, |ui| add_contents(ui, how_expanded));
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show either a collapsed or a expanded panel, with a nice animation between.
|
||||||
|
pub fn show_animated_between_inside<R>(
|
||||||
|
ui: &mut Ui,
|
||||||
|
is_expanded: bool,
|
||||||
|
collapsed_panel: Self,
|
||||||
|
expanded_panel: Self,
|
||||||
|
add_contents: impl FnOnce(&mut Ui, f32) -> R,
|
||||||
|
) -> InnerResponse<R> {
|
||||||
|
let how_expanded = ui
|
||||||
|
.ctx()
|
||||||
|
.animate_bool(expanded_panel.id.with("animation"), is_expanded);
|
||||||
|
|
||||||
|
if 0.0 == how_expanded {
|
||||||
|
collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
|
||||||
|
} else if how_expanded < 1.0 {
|
||||||
|
// Show animation:
|
||||||
|
let collapsed_width = PanelState::load(ui.ctx(), collapsed_panel.id)
|
||||||
|
.map_or(collapsed_panel.default_width, |state| state.rect.width());
|
||||||
|
let expanded_width = PanelState::load(ui.ctx(), expanded_panel.id)
|
||||||
|
.map_or(expanded_panel.default_width, |state| state.rect.width());
|
||||||
|
let fake_width = lerp(collapsed_width..=expanded_width, how_expanded);
|
||||||
|
Self {
|
||||||
|
id: expanded_panel.id.with("animating_panel"),
|
||||||
|
..expanded_panel
|
||||||
|
}
|
||||||
|
.resizable(false)
|
||||||
|
.exact_width(fake_width)
|
||||||
|
.show_inside(ui, |ui| add_contents(ui, how_expanded))
|
||||||
|
} else {
|
||||||
|
expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -390,21 +525,21 @@ pub struct TopBottomPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TopBottomPanel {
|
impl TopBottomPanel {
|
||||||
/// `id_source`: Something unique, e.g. `"my_top_panel"`.
|
/// The id should be globally unique, e.g. `Id::new("my_top_panel")`.
|
||||||
pub fn top(id_source: impl std::hash::Hash) -> Self {
|
pub fn top(id: impl Into<Id>) -> Self {
|
||||||
Self::new(TopBottomSide::Top, id_source)
|
Self::new(TopBottomSide::Top, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `id_source`: Something unique, e.g. `"my_bottom_panel"`.
|
/// The id should be globally unique, e.g. `Id::new("my_bottom_panel")`.
|
||||||
pub fn bottom(id_source: impl std::hash::Hash) -> Self {
|
pub fn bottom(id: impl Into<Id>) -> Self {
|
||||||
Self::new(TopBottomSide::Bottom, id_source)
|
Self::new(TopBottomSide::Bottom, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `id_source`: Something unique, e.g. `"my_panel"`.
|
/// The id should be globally unique, e.g. `Id::new("my_panel")`.
|
||||||
pub fn new(side: TopBottomSide, id_source: impl std::hash::Hash) -> Self {
|
pub fn new(side: TopBottomSide, id: impl Into<Id>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
side,
|
side,
|
||||||
id: Id::new(id_source),
|
id: id.into(),
|
||||||
frame: None,
|
frame: None,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
default_height: None,
|
default_height: None,
|
||||||
|
@ -632,6 +767,151 @@ impl TopBottomPanel {
|
||||||
|
|
||||||
inner_response
|
inner_response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show the panel if `is_expanded` is `true`,
|
||||||
|
/// otherwise don't show it, but with a nice animation between collapsed and expanded.
|
||||||
|
pub fn show_animated<R>(
|
||||||
|
self,
|
||||||
|
ctx: &Context,
|
||||||
|
is_expanded: bool,
|
||||||
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
|
) -> Option<InnerResponse<R>> {
|
||||||
|
let how_expanded = ctx.animate_bool(self.id.with("animation"), is_expanded);
|
||||||
|
|
||||||
|
if 0.0 == how_expanded {
|
||||||
|
None
|
||||||
|
} else if how_expanded < 1.0 {
|
||||||
|
// Show a fake panel in this in-between animation state:
|
||||||
|
let expanded_height = PanelState::load(ctx, self.id)
|
||||||
|
.map(|state| state.rect.height())
|
||||||
|
.or(self.default_height)
|
||||||
|
.unwrap_or_else(|| ctx.style().spacing.interact_size.y);
|
||||||
|
let fake_height = how_expanded * expanded_height;
|
||||||
|
Self {
|
||||||
|
id: self.id.with("animating_panel"),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
.resizable(false)
|
||||||
|
.exact_height(fake_height)
|
||||||
|
.show(ctx, |_ui| {});
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// Show the real panel:
|
||||||
|
Some(self.show(ctx, add_contents))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show the panel if `is_expanded` is `true`,
|
||||||
|
/// otherwise don't show it, but with a nice animation between collapsed and expanded.
|
||||||
|
pub fn show_animated_inside<R>(
|
||||||
|
self,
|
||||||
|
ui: &mut Ui,
|
||||||
|
is_expanded: bool,
|
||||||
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
|
) -> Option<InnerResponse<R>> {
|
||||||
|
let how_expanded = ui
|
||||||
|
.ctx()
|
||||||
|
.animate_bool(self.id.with("animation"), is_expanded);
|
||||||
|
|
||||||
|
if 0.0 == how_expanded {
|
||||||
|
None
|
||||||
|
} else if how_expanded < 1.0 {
|
||||||
|
// Show a fake panel in this in-between animation state:
|
||||||
|
let expanded_height = PanelState::load(ui.ctx(), self.id)
|
||||||
|
.map(|state| state.rect.height())
|
||||||
|
.or(self.default_height)
|
||||||
|
.unwrap_or_else(|| ui.style().spacing.interact_size.y);
|
||||||
|
let fake_height = how_expanded * expanded_height;
|
||||||
|
Self {
|
||||||
|
id: self.id.with("animating_panel"),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
.resizable(false)
|
||||||
|
.exact_height(fake_height)
|
||||||
|
.show_inside(ui, |_ui| {});
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// Show the real panel:
|
||||||
|
Some(self.show_inside(ui, add_contents))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show either a collapsed or a expanded panel, with a nice animation between.
|
||||||
|
pub fn show_animated_between<R>(
|
||||||
|
ctx: &Context,
|
||||||
|
is_expanded: bool,
|
||||||
|
collapsed_panel: Self,
|
||||||
|
expanded_panel: Self,
|
||||||
|
add_contents: impl FnOnce(&mut Ui, f32) -> R,
|
||||||
|
) -> Option<InnerResponse<R>> {
|
||||||
|
let how_expanded = ctx.animate_bool(expanded_panel.id.with("animation"), is_expanded);
|
||||||
|
|
||||||
|
if 0.0 == how_expanded {
|
||||||
|
Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
|
||||||
|
} else if how_expanded < 1.0 {
|
||||||
|
// Show animation:
|
||||||
|
let collapsed_height = PanelState::load(ctx, collapsed_panel.id)
|
||||||
|
.map(|state| state.rect.height())
|
||||||
|
.or(collapsed_panel.default_height)
|
||||||
|
.unwrap_or_else(|| ctx.style().spacing.interact_size.y);
|
||||||
|
|
||||||
|
let expanded_height = PanelState::load(ctx, expanded_panel.id)
|
||||||
|
.map(|state| state.rect.height())
|
||||||
|
.or(expanded_panel.default_height)
|
||||||
|
.unwrap_or_else(|| ctx.style().spacing.interact_size.y);
|
||||||
|
|
||||||
|
let fake_height = lerp(collapsed_height..=expanded_height, how_expanded);
|
||||||
|
Self {
|
||||||
|
id: expanded_panel.id.with("animating_panel"),
|
||||||
|
..expanded_panel
|
||||||
|
}
|
||||||
|
.resizable(false)
|
||||||
|
.exact_height(fake_height)
|
||||||
|
.show(ctx, |ui| add_contents(ui, how_expanded));
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show either a collapsed or a expanded panel, with a nice animation between.
|
||||||
|
pub fn show_animated_between_inside<R>(
|
||||||
|
ui: &mut Ui,
|
||||||
|
is_expanded: bool,
|
||||||
|
collapsed_panel: Self,
|
||||||
|
expanded_panel: Self,
|
||||||
|
add_contents: impl FnOnce(&mut Ui, f32) -> R,
|
||||||
|
) -> InnerResponse<R> {
|
||||||
|
let how_expanded = ui
|
||||||
|
.ctx()
|
||||||
|
.animate_bool(expanded_panel.id.with("animation"), is_expanded);
|
||||||
|
|
||||||
|
if 0.0 == how_expanded {
|
||||||
|
collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
|
||||||
|
} else if how_expanded < 1.0 {
|
||||||
|
// Show animation:
|
||||||
|
let collapsed_height = PanelState::load(ui.ctx(), collapsed_panel.id)
|
||||||
|
.map(|state| state.rect.height())
|
||||||
|
.or(collapsed_panel.default_height)
|
||||||
|
.unwrap_or_else(|| ui.style().spacing.interact_size.y);
|
||||||
|
|
||||||
|
let expanded_height = PanelState::load(ui.ctx(), expanded_panel.id)
|
||||||
|
.map(|state| state.rect.height())
|
||||||
|
.or(expanded_panel.default_height)
|
||||||
|
.unwrap_or_else(|| ui.style().spacing.interact_size.y);
|
||||||
|
|
||||||
|
let fake_height = lerp(collapsed_height..=expanded_height, how_expanded);
|
||||||
|
Self {
|
||||||
|
id: expanded_panel.id.with("animating_panel"),
|
||||||
|
..expanded_panel
|
||||||
|
}
|
||||||
|
.resizable(false)
|
||||||
|
.exact_height(fake_height)
|
||||||
|
.show_inside(ui, |ui| add_contents(ui, how_expanded))
|
||||||
|
} else {
|
||||||
|
expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
|
@ -77,6 +77,14 @@ impl std::fmt::Debug for Id {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience
|
||||||
|
impl From<&'static str> for Id {
|
||||||
|
#[inline]
|
||||||
|
fn from(string: &'static str) -> Self {
|
||||||
|
Self::new(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Idea taken from the `nohash_hasher` crate.
|
// Idea taken from the `nohash_hasher` crate.
|
||||||
|
|
|
@ -200,19 +200,8 @@ impl eframe::App for WrapApp {
|
||||||
|
|
||||||
self.state.backend_panel.update(ctx, frame);
|
self.state.backend_panel.update(ctx, frame);
|
||||||
|
|
||||||
if !is_mobile(ctx)
|
if !is_mobile(ctx) {
|
||||||
&& (self.state.backend_panel.open || ctx.memory().everything_is_visible())
|
self.backend_panel(ctx, frame);
|
||||||
{
|
|
||||||
egui::SidePanel::left("backend_panel")
|
|
||||||
.resizable(false)
|
|
||||||
.show(ctx, |ui| {
|
|
||||||
ui.vertical_centered(|ui| {
|
|
||||||
ui.heading("💻 Backend");
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
self.backend_panel_contents(ui, frame);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.show_selected_app(ctx, frame);
|
self.show_selected_app(ctx, frame);
|
||||||
|
@ -236,6 +225,23 @@ impl eframe::App for WrapApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WrapApp {
|
impl WrapApp {
|
||||||
|
fn backend_panel(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
|
// The backend-panel can be toggled on/off.
|
||||||
|
// We show a little animation when the user switches it.
|
||||||
|
let is_open = self.state.backend_panel.open || ctx.memory().everything_is_visible();
|
||||||
|
|
||||||
|
egui::SidePanel::left("backend_panel")
|
||||||
|
.resizable(false)
|
||||||
|
.show_animated(ctx, is_open, |ui| {
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
ui.heading("💻 Backend");
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
self.backend_panel_contents(ui, frame);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn backend_panel_contents(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
fn backend_panel_contents(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||||
self.state.backend_panel.ui(ui, frame);
|
self.state.backend_panel.ui(ui, frame);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue