[window] make resizing a bit smoother
This commit is contained in:
parent
1c9fdc3a48
commit
d0f5181197
8 changed files with 81 additions and 70 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -348,7 +348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "egui"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"ahash 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"criterion 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -363,7 +363,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"egui 0.1.0",
|
||||
"egui 0.1.1",
|
||||
"glium 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"webbrowser 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -373,7 +373,7 @@ dependencies = [
|
|||
name = "egui_wasm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"egui 0.1.0",
|
||||
"egui 0.1.1",
|
||||
"js-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -390,7 +390,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
name = "example_glium"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"egui 0.1.0",
|
||||
"egui 0.1.1",
|
||||
"egui_glium 0.1.0",
|
||||
"glium 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -401,7 +401,7 @@ dependencies = [
|
|||
name = "example_wasm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"egui 0.1.0",
|
||||
"egui 0.1.1",
|
||||
"egui_wasm 0.1.0",
|
||||
"serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -227,7 +227,7 @@ impl Prepared {
|
|||
|
||||
fn mouse_pressed_on_area(ctx: &Context, layer: Layer) -> bool {
|
||||
if let Some(mouse_pos) = ctx.input().mouse.pos {
|
||||
ctx.input().mouse.pressed && ctx.memory().layer_at(mouse_pos) == Some(layer)
|
||||
ctx.input().mouse.pressed && ctx.layer_at(mouse_pos) == Some(layer)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
|
@ -25,9 +25,8 @@ pub struct Resize {
|
|||
/// If false, we are no enabled
|
||||
resizable: bool,
|
||||
|
||||
// Will still try to stay within parent ui bounds
|
||||
min_size: Vec2,
|
||||
max_size: Vec2,
|
||||
min_content_size: Vec2,
|
||||
min_desired_size: Vec2,
|
||||
|
||||
default_size: Vec2,
|
||||
|
||||
|
@ -40,8 +39,8 @@ impl Default for Resize {
|
|||
Self {
|
||||
id: None,
|
||||
resizable: true,
|
||||
min_size: Vec2::splat(16.0),
|
||||
max_size: Vec2::infinity(),
|
||||
min_content_size: Vec2::splat(16.0),
|
||||
min_desired_size: vec2(200.0, 400.0),
|
||||
default_size: vec2(280.0, 400.0), // TODO: perferred size for a resizable area (e.g. a window)
|
||||
outline: true,
|
||||
handle_offset: Default::default(),
|
||||
|
@ -84,13 +83,15 @@ impl Resize {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn min_size(mut self, min_size: impl Into<Vec2>) -> Self {
|
||||
self.min_size = min_size.into();
|
||||
/// Won't shrink to smaller than this
|
||||
pub fn min_content_size(mut self, min_content_size: impl Into<Vec2>) -> Self {
|
||||
self.min_content_size = min_content_size.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_size(mut self, max_size: impl Into<Vec2>) -> Self {
|
||||
self.max_size = max_size.into();
|
||||
/// Won't shrink to smaller than this
|
||||
pub fn min_desired_size(mut self, min_desired_size: impl Into<Vec2>) -> Self {
|
||||
self.min_desired_size = min_desired_size.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -114,17 +115,12 @@ impl Resize {
|
|||
pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
|
||||
let size = size.into();
|
||||
self.default_size = size;
|
||||
self.min_size = size;
|
||||
self.max_size = size;
|
||||
self.min_content_size = size;
|
||||
self.min_desired_size = size;
|
||||
self.resizable = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn as_wide_as_possible(mut self) -> Self {
|
||||
self.min_size.x = f32::INFINITY;
|
||||
self
|
||||
}
|
||||
|
||||
/// Offset the position of the resize handle by this much
|
||||
pub fn handle_offset(mut self, handle_offset: impl Into<Vec2>) -> Self {
|
||||
self.handle_offset = handle_offset.into();
|
||||
|
@ -147,12 +143,9 @@ struct Prepared {
|
|||
impl Resize {
|
||||
fn begin(&mut self, ui: &mut Ui) -> Prepared {
|
||||
let id = self.id.unwrap_or_else(|| ui.make_child_id("resize"));
|
||||
self.min_size = self.min_size.min(ui.available().size());
|
||||
self.max_size = self.max_size.min(ui.available().size());
|
||||
self.max_size = self.max_size.max(self.min_size);
|
||||
|
||||
let mut state = ui.memory().resize.get(&id).cloned().unwrap_or_else(|| {
|
||||
let default_size = self.default_size.clamp(self.min_size..=self.max_size);
|
||||
let default_size = self.default_size.max(self.min_content_size);
|
||||
|
||||
State {
|
||||
desired_size: default_size,
|
||||
|
@ -161,7 +154,7 @@ impl Resize {
|
|||
}
|
||||
});
|
||||
|
||||
state.desired_size = state.desired_size.clamp(self.min_size..=self.max_size);
|
||||
state.desired_size = state.desired_size.max(self.min_desired_size);
|
||||
|
||||
let position = ui.available().min;
|
||||
|
||||
|
@ -176,14 +169,8 @@ impl Resize {
|
|||
|
||||
if corner_interact.active {
|
||||
if let Some(mouse_pos) = ui.input().mouse.pos {
|
||||
// This is the desired size. We may not be able to achieve it.
|
||||
|
||||
state.desired_size = mouse_pos - position + 0.5 * corner_interact.rect.size()
|
||||
- self.handle_offset;
|
||||
// We don't clamp to max size, because we want to be able to push against outer bounds.
|
||||
// For instance, if we are inside a bigger Resize region, we want to expand that.
|
||||
// state.desired_size = state.desired_size.clamp(self.min_size..=self.max_size);
|
||||
state.desired_size = state.desired_size.max(self.min_size);
|
||||
}
|
||||
}
|
||||
Some(corner_interact)
|
||||
|
@ -193,11 +180,8 @@ impl Resize {
|
|||
|
||||
if let Some(requested_size) = state.requested_size.take() {
|
||||
state.desired_size = requested_size;
|
||||
// We don't clamp to max size, because we want to be able to push against outer bounds.
|
||||
// For instance, if we are inside a bigger Resize region, we want to expand that.
|
||||
// state.desired_size = state.desired_size.clamp(self.min_size..=self.max_size);
|
||||
state.desired_size = state.desired_size.max(self.min_size);
|
||||
}
|
||||
state.desired_size = state.desired_size.max(self.min_desired_size);
|
||||
|
||||
// ------------------------------
|
||||
|
||||
|
@ -283,6 +267,19 @@ impl Resize {
|
|||
}
|
||||
|
||||
ui.memory().resize.insert(id, state);
|
||||
|
||||
if ui.ctx().style().debug_resize {
|
||||
ui.ctx().debug_rect(
|
||||
Rect::from_min_size(content_ui.top_left(), state.desired_size),
|
||||
color::GREEN,
|
||||
"desired_size",
|
||||
);
|
||||
ui.ctx().debug_rect(
|
||||
Rect::from_min_size(content_ui.top_left(), state.last_content_size),
|
||||
color::LIGHT_BLUE,
|
||||
"last_content_size",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,10 @@ impl<'open> Window<'open> {
|
|||
open: None,
|
||||
area,
|
||||
frame: None,
|
||||
resize: Resize::default().outline(false),
|
||||
resize: Resize::default()
|
||||
.outline(false)
|
||||
.min_content_size([96.0, 32.0])
|
||||
.min_desired_size([96.0, 200.0]),
|
||||
scroll: Some(
|
||||
ScrollArea::default()
|
||||
.always_show_scroll(false)
|
||||
|
@ -78,16 +81,6 @@ impl<'open> Window<'open> {
|
|||
self.default_pos(rect.min).default_size(rect.size())
|
||||
}
|
||||
|
||||
pub fn min_size(mut self, min_size: impl Into<Vec2>) -> Self {
|
||||
self.resize = self.resize.min_size(min_size);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_size(mut self, max_size: impl Into<Vec2>) -> Self {
|
||||
self.resize = self.resize.max_size(max_size);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
|
||||
self.resize = self.resize.fixed_size(size);
|
||||
self
|
||||
|
@ -165,8 +158,12 @@ impl<'open> Window<'open> {
|
|||
// First interact (move etc) to avoid frame delay:
|
||||
let last_frame_outer_rect = area.state().rect();
|
||||
let interaction = if possible.movable || possible.resizable {
|
||||
let title_bar_height =
|
||||
title_label.font_height(ctx.fonts()) + 1.0 * ctx.style().item_spacing.y; // this could be better
|
||||
let margins = 2.0 * frame.margin + vec2(0.0, title_bar_height);
|
||||
interact(
|
||||
ctx,
|
||||
margins,
|
||||
possible,
|
||||
area_layer,
|
||||
area.state_mut(),
|
||||
|
@ -295,6 +292,7 @@ impl WindowInteraction {
|
|||
|
||||
fn interact(
|
||||
ctx: &Context,
|
||||
margins: Vec2,
|
||||
possible: PossibleInteractions,
|
||||
area_layer: Layer,
|
||||
area_state: &mut area::State,
|
||||
|
@ -302,7 +300,6 @@ fn interact(
|
|||
resize_id: Id,
|
||||
rect: Rect,
|
||||
) -> Option<WindowInteraction> {
|
||||
let pre_resize = ctx.round_rect_to_pixels(rect);
|
||||
let window_interaction = window_interaction(
|
||||
ctx,
|
||||
possible,
|
||||
|
@ -317,12 +314,11 @@ fn interact(
|
|||
|
||||
area_state.pos = new_rect.min;
|
||||
|
||||
let mut resize_state = ctx.memory().resize.get(&resize_id).cloned().unwrap();
|
||||
// resize_state.size += new_rect.size() - pre_resize.size();
|
||||
// resize_state.size = new_rect.size() - some margin;
|
||||
resize_state.requested_size =
|
||||
Some(resize_state.desired_size + new_rect.size() - pre_resize.size());
|
||||
ctx.memory().resize.insert(resize_id, resize_state);
|
||||
if window_interaction.is_resize() {
|
||||
let mut resize_state = ctx.memory().resize.get(&resize_id).cloned().unwrap();
|
||||
resize_state.requested_size = Some(new_rect.size() - margins);
|
||||
ctx.memory().resize.insert(resize_id, resize_state);
|
||||
}
|
||||
|
||||
ctx.memory().areas.move_to_top(area_layer);
|
||||
Some(window_interaction)
|
||||
|
@ -400,7 +396,7 @@ fn resize_hover(
|
|||
rect: Rect,
|
||||
) -> Option<WindowInteraction> {
|
||||
if let Some(mouse_pos) = ctx.input().mouse.pos {
|
||||
if let Some(top_layer) = ctx.memory().layer_at(mouse_pos) {
|
||||
if let Some(top_layer) = ctx.layer_at(mouse_pos) {
|
||||
if top_layer != area_layer && top_layer.order != Order::Background {
|
||||
return None; // Another window is on top here
|
||||
}
|
||||
|
@ -411,8 +407,8 @@ fn resize_hover(
|
|||
return None;
|
||||
}
|
||||
|
||||
let side_interact_radius = 5.0; // TODO: from style
|
||||
let corner_interact_radius = 10.0; // TODO
|
||||
let side_interact_radius = ctx.style().resize_interact_radius_side;
|
||||
let corner_interact_radius = ctx.style().resize_interact_radius_corner;
|
||||
if rect.expand(side_interact_radius).contains(mouse_pos) {
|
||||
let (mut left, mut right, mut top, mut bottom) = Default::default();
|
||||
if possible.resizable {
|
||||
|
@ -531,7 +527,7 @@ fn show_title_bar(
|
|||
collapsing: &mut collapsing_header::State,
|
||||
) -> TitleBar {
|
||||
let title_bar_and_rect = ui.inner_layout(Layout::horizontal(Align::Center), |ui| {
|
||||
ui.set_desired_height(title_label.font_height(ui));
|
||||
ui.set_desired_height(title_label.font_height(ui.fonts()));
|
||||
|
||||
let item_spacing = ui.style().item_spacing;
|
||||
let button_size = ui.style().start_icon_width;
|
||||
|
|
|
@ -258,10 +258,17 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
pub fn layer_at(&self, pos: Pos2) -> Option<Layer> {
|
||||
let resize_interact_radius_side = self.style().resize_interact_radius_side;
|
||||
self.memory().layer_at(pos, resize_interact_radius_side)
|
||||
}
|
||||
|
||||
pub fn contains_mouse(&self, layer: Layer, clip_rect: Rect, rect: Rect) -> bool {
|
||||
let rect = rect.intersect(clip_rect);
|
||||
if let Some(mouse_pos) = self.input.mouse.pos {
|
||||
rect.contains(mouse_pos) && self.memory().layer_at(mouse_pos) == Some(layer)
|
||||
rect.contains(mouse_pos) && self.layer_at(mouse_pos) == Some(layer)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
@ -399,7 +406,7 @@ impl Context {
|
|||
);
|
||||
}
|
||||
|
||||
pub fn debug_rect(&self, rect: Rect, name: impl Into<String>) {
|
||||
pub fn debug_rect(&self, rect: Rect, color: Color, name: impl Into<String>) {
|
||||
let text = format!("{} {:?}", name.into(), rect);
|
||||
let layer = Layer::debug();
|
||||
self.add_paint_cmd(
|
||||
|
@ -407,13 +414,13 @@ impl Context {
|
|||
PaintCmd::Rect {
|
||||
corner_radius: 0.0,
|
||||
fill: None,
|
||||
outline: Some(LineStyle::new(1.0, color::RED)),
|
||||
outline: Some(LineStyle::new(2.0, color)),
|
||||
rect,
|
||||
},
|
||||
);
|
||||
let align = (Align::Min, Align::Min);
|
||||
let text_style = TextStyle::Monospace;
|
||||
self.floating_text(layer, rect.min, text, text_style, align, Some(color::RED));
|
||||
self.floating_text(layer, rect.min, text, text_style, align, Some(color));
|
||||
}
|
||||
|
||||
/// Show some text anywhere on screen.
|
||||
|
|
|
@ -112,9 +112,8 @@ impl Memory {
|
|||
self.areas.end_frame()
|
||||
}
|
||||
|
||||
/// TODO: call once at the start of the frame for the current mouse pos
|
||||
pub fn layer_at(&self, pos: Pos2) -> Option<Layer> {
|
||||
self.areas.layer_at(pos)
|
||||
pub fn layer_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option<Layer> {
|
||||
self.areas.layer_at(pos, resize_interact_radius_side)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,13 +138,14 @@ impl Areas {
|
|||
}
|
||||
}
|
||||
|
||||
/// TODO: call once at the start of the frame for the current mouse pos
|
||||
pub fn layer_at(&self, pos: Pos2) -> Option<Layer> {
|
||||
pub fn layer_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option<Layer> {
|
||||
for layer in self.order.iter().rev() {
|
||||
if self.is_visible(layer) {
|
||||
if let Some(state) = self.areas.get(&layer.id) {
|
||||
if state.interactable {
|
||||
let rect = Rect::from_min_size(state.pos, state.size);
|
||||
// Allow us to resize by dragging just outside the window:
|
||||
let rect = rect.expand(resize_interact_radius_side);
|
||||
if rect.contains(pos) {
|
||||
return Some(*layer);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,12 @@ pub struct Style {
|
|||
/// The text starts after this many pixels.
|
||||
pub start_icon_width: f32,
|
||||
|
||||
/// Mouse must be the close to the side of a window to resize
|
||||
pub resize_interact_radius_side: f32,
|
||||
|
||||
/// Mouse must be the close to the corner of a window to resize
|
||||
pub resize_interact_radius_corner: f32,
|
||||
|
||||
// -----------------------------------------------
|
||||
// Purely visual:
|
||||
pub interact: Interact,
|
||||
|
@ -60,6 +66,7 @@ pub struct Style {
|
|||
// -----------------------------------------------
|
||||
// Debug rendering:
|
||||
pub debug_widget_rects: bool,
|
||||
pub debug_resize: bool,
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
|
@ -71,6 +78,8 @@ impl Default for Style {
|
|||
indent: 21.0,
|
||||
clickable_diameter: 22.0,
|
||||
start_icon_width: 14.0,
|
||||
resize_interact_radius_side: 5.0,
|
||||
resize_interact_radius_corner: 10.0,
|
||||
interact: Default::default(),
|
||||
text_color: gray(160, 255),
|
||||
line_width: 1.0,
|
||||
|
@ -84,6 +93,7 @@ impl Default for Style {
|
|||
menu_bar: MenuBar::default(),
|
||||
clip_rect_margin: 3.0,
|
||||
debug_widget_rects: false,
|
||||
debug_resize: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -220,6 +230,7 @@ impl Style {
|
|||
}
|
||||
|
||||
ui.add(Checkbox::new(&mut self.debug_widget_rects, "Paint debug rectangles around widgets"));
|
||||
ui.add(Checkbox::new(&mut self.debug_resize, "Debug Resize"));
|
||||
|
||||
ui.add(Slider::f32(&mut self.item_spacing.x, 0.0..=10.0).text("item_spacing.x").precision(0));
|
||||
ui.add(Slider::f32(&mut self.item_spacing.y, 0.0..=10.0).text("item_spacing.y").precision(0));
|
||||
|
|
|
@ -81,8 +81,8 @@ impl Label {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn font_height(&self, ui: &Ui) -> f32 {
|
||||
ui.fonts()[self.text_style].height()
|
||||
pub fn font_height(&self, fonts: &Fonts) -> f32 {
|
||||
fonts[self.text_style].height()
|
||||
}
|
||||
|
||||
// TODO: this should return a LabelLayout which has a paint method.
|
||||
|
|
Loading…
Reference in a new issue