Resize windows by dragging any side or corner
This commit is contained in:
parent
f9bb9f71c4
commit
88bfcd585e
13 changed files with 353 additions and 16 deletions
|
@ -63,6 +63,7 @@
|
||||||
let output = JSON.parse(wasm_bindgen.run_gui(g_wasm_app, JSON.stringify(input)));
|
let output = JSON.parse(wasm_bindgen.run_gui(g_wasm_app, JSON.stringify(input)));
|
||||||
// console.log(`output: ${JSON.stringify(output)}`);
|
// console.log(`output: ${JSON.stringify(output)}`);
|
||||||
document.body.style.cursor = from_emigui_cursor(output.cursor_icon);
|
document.body.style.cursor = from_emigui_cursor(output.cursor_icon);
|
||||||
|
// console.log(`Translated ${output.cursor_icon} to ${document.body.style.cursor}`);
|
||||||
if (output.open_url) {
|
if (output.open_url) {
|
||||||
window.open(output.open_url, "_self");
|
window.open(output.open_url, "_self");
|
||||||
}
|
}
|
||||||
|
@ -71,7 +72,10 @@
|
||||||
function from_emigui_cursor(cursor) {
|
function from_emigui_cursor(cursor) {
|
||||||
if (cursor == "no_drop") { return "no-drop"; }
|
if (cursor == "no_drop") { return "no-drop"; }
|
||||||
else if (cursor == "not_allowed") { return "not-allowed"; }
|
else if (cursor == "not_allowed") { return "not-allowed"; }
|
||||||
|
else if (cursor == "resize_horizontal") { return "ew-resize"; }
|
||||||
|
else if (cursor == "resize_ne_sw") { return "nesw-resize"; }
|
||||||
else if (cursor == "resize_nw_se") { return "nwse-resize"; }
|
else if (cursor == "resize_nw_se") { return "nwse-resize"; }
|
||||||
|
else if (cursor == "resize_vertical") { return "ns-resize"; }
|
||||||
else if (cursor == "pointing_hand") { return "pointer"; }
|
else if (cursor == "pointing_hand") { return "pointer"; }
|
||||||
// TODO: more
|
// TODO: more
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -15,7 +15,7 @@ This is the core library crate Emigui. It is fully platform independent without
|
||||||
* [x] Kinetic windows
|
* [x] Kinetic windows
|
||||||
* [ ] Windows should open from `UI`s and be boxed by parent ui.
|
* [ ] Windows should open from `UI`s and be boxed by parent ui.
|
||||||
* Then we could open the example app inside a window in the example app, recursively.
|
* Then we could open the example app inside a window in the example app, recursively.
|
||||||
* [ ] Resize any side and corner on windows
|
* [x] Resize any side and corner on windows
|
||||||
* [ ] Fix autoshrink
|
* [ ] Fix autoshrink
|
||||||
* [ ] Scroll areas
|
* [ ] Scroll areas
|
||||||
* [x] Vertical scrolling
|
* [x] Vertical scrolling
|
||||||
|
|
|
@ -46,12 +46,24 @@ impl Area {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn layer(&self) -> Layer {
|
||||||
|
Layer {
|
||||||
|
order: self.order,
|
||||||
|
id: self.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// moveable by draggin the area?
|
||||||
pub fn movable(mut self, movable: bool) -> Self {
|
pub fn movable(mut self, movable: bool) -> Self {
|
||||||
self.movable = movable;
|
self.movable = movable;
|
||||||
self.interactable |= movable;
|
self.interactable |= movable;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_movable(&self) -> bool {
|
||||||
|
self.movable
|
||||||
|
}
|
||||||
|
|
||||||
/// If false, clicks goes stright throught to what is behind us.
|
/// If false, clicks goes stright throught to what is behind us.
|
||||||
/// Good for tooltips etc.
|
/// Good for tooltips etc.
|
||||||
pub fn interactable(mut self, interactable: bool) -> Self {
|
pub fn interactable(mut self, interactable: bool) -> Self {
|
||||||
|
|
|
@ -34,6 +34,14 @@ impl State {
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper
|
||||||
|
pub fn is_open(ctx: &Context, id: Id) -> Option<bool> {
|
||||||
|
ctx.memory()
|
||||||
|
.collapsing_headers
|
||||||
|
.get(&id)
|
||||||
|
.map(|state| state.open)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toggle(&mut self, ui: &Ui) {
|
pub fn toggle(&mut self, ui: &Ui) {
|
||||||
self.open = !self.open;
|
self.open = !self.open;
|
||||||
self.toggle_time = ui.input().time;
|
self.toggle_time = ui.input().time;
|
||||||
|
|
|
@ -4,6 +4,7 @@ use crate::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Frame {
|
pub struct Frame {
|
||||||
|
// On each side
|
||||||
pub margin: Vec2,
|
pub margin: Vec2,
|
||||||
pub corner_radius: f32,
|
pub corner_radius: f32,
|
||||||
pub fill_color: Option<Color>,
|
pub fill_color: Option<Color>,
|
||||||
|
@ -68,7 +69,7 @@ impl Frame {
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let outer_rect = ui.available();
|
let outer_rect = ui.available();
|
||||||
let inner_rect = outer_rect.expand2(-margin);
|
let inner_rect = outer_rect.shrink2(margin);
|
||||||
let where_to_put_background = ui.paint_list_len();
|
let where_to_put_background = ui.paint_list_len();
|
||||||
|
|
||||||
let mut child_ui = ui.child_ui(inner_rect);
|
let mut child_ui = ui.child_ui(inner_rect);
|
||||||
|
|
|
@ -4,12 +4,17 @@ use crate::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, serde_derive::Deserialize, serde_derive::Serialize)]
|
#[derive(Clone, Copy, Debug, serde_derive::Deserialize, serde_derive::Serialize)]
|
||||||
pub(crate) struct State {
|
pub(crate) struct State {
|
||||||
size: Vec2,
|
pub(crate) size: Vec2,
|
||||||
|
|
||||||
|
/// Externally requested size (e.g. by Window) for the next frame
|
||||||
|
pub(crate) requested_size: Option<Vec2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: auto-shink/grow should be part of another container!
|
// TODO: auto-shink/grow should be part of another container!
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Resize {
|
pub struct Resize {
|
||||||
|
id: Option<Id>,
|
||||||
|
|
||||||
/// If false, we are no enabled
|
/// If false, we are no enabled
|
||||||
resizable: bool,
|
resizable: bool,
|
||||||
|
|
||||||
|
@ -34,6 +39,7 @@ pub struct Resize {
|
||||||
impl Default for Resize {
|
impl Default for Resize {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
id: None,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
min_size: Vec2::splat(16.0),
|
min_size: Vec2::splat(16.0),
|
||||||
max_size: Vec2::infinity(),
|
max_size: Vec2::infinity(),
|
||||||
|
@ -49,6 +55,12 @@ impl Default for Resize {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resize {
|
impl Resize {
|
||||||
|
/// Assign an explicit and globablly unique id.
|
||||||
|
pub fn id(mut self, id: Id) -> Self {
|
||||||
|
self.id = Some(id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn default_width(mut self, width: f32) -> Self {
|
pub fn default_width(mut self, width: f32) -> Self {
|
||||||
self.default_size.x = width;
|
self.default_size.x = width;
|
||||||
self
|
self
|
||||||
|
@ -81,6 +93,10 @@ impl Resize {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_resizable(&self) -> bool {
|
||||||
|
self.resizable
|
||||||
|
}
|
||||||
|
|
||||||
/// Not resizable, just takes the size of its contents.
|
/// Not resizable, just takes the size of its contents.
|
||||||
pub fn auto_sized(self) -> Self {
|
pub fn auto_sized(self) -> Self {
|
||||||
self.default_size(Vec2::splat(f32::INFINITY))
|
self.default_size(Vec2::splat(f32::INFINITY))
|
||||||
|
@ -152,10 +168,9 @@ impl Resize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: a common trait for Things that follow this pattern
|
|
||||||
impl Resize {
|
impl Resize {
|
||||||
pub fn show(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) {
|
pub fn show(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) {
|
||||||
let id = ui.make_child_id("resize");
|
let id = self.id.unwrap_or_else(|| ui.make_child_id("resize"));
|
||||||
self.min_size = self.min_size.min(ui.available().size());
|
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.min(ui.available().size());
|
||||||
self.max_size = self.max_size.max(self.min_size);
|
self.max_size = self.max_size.max(self.min_size);
|
||||||
|
@ -164,7 +179,13 @@ impl Resize {
|
||||||
Some(state) => (false, *state),
|
Some(state) => (false, *state),
|
||||||
None => {
|
None => {
|
||||||
let default_size = self.default_size.clamp(self.min_size..=self.max_size);
|
let default_size = self.default_size.clamp(self.min_size..=self.max_size);
|
||||||
(true, State { size: default_size })
|
(
|
||||||
|
true,
|
||||||
|
State {
|
||||||
|
size: default_size,
|
||||||
|
requested_size: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -199,6 +220,14 @@ impl Resize {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(requested_size) = state.requested_size.take() {
|
||||||
|
state.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.size = state.size.clamp(self.min_size..=self.max_size);
|
||||||
|
state.size = state.size.max(self.min_size);
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
|
|
||||||
let inner_rect = Rect::from_min_size(position, state.size);
|
let inner_rect = Rect::from_min_size(position, state.size);
|
||||||
|
|
|
@ -100,10 +100,14 @@ impl ScrollArea {
|
||||||
inner_rect.size() + vec2(current_scroll_bar_width, 0.0),
|
inner_rect.size() + vec2(current_scroll_bar_width, 0.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let content_interact = outer_ui.interact_rect(inner_rect, scroll_area_id.with("area"));
|
let content_is_too_small = content_size.y > inner_size.y;
|
||||||
if content_interact.active {
|
|
||||||
// Dragging scroll area to scroll:
|
if content_is_too_small {
|
||||||
state.offset.y -= ctx.input().mouse_move.y;
|
// Dragg contents to scroll (for touch screens mostly):
|
||||||
|
let content_interact = outer_ui.interact_rect(inner_rect, scroll_area_id.with("area"));
|
||||||
|
if content_interact.active {
|
||||||
|
state.offset.y -= ctx.input().mouse_move.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check that nothing else is being inteacted with
|
// TODO: check that nothing else is being inteacted with
|
||||||
|
@ -111,7 +115,7 @@ impl ScrollArea {
|
||||||
state.offset.y -= ctx.input().scroll_delta.y;
|
state.offset.y -= ctx.input().scroll_delta.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
let show_scroll_this_frame = content_size.y > inner_size.y || self.always_show_scroll;
|
let show_scroll_this_frame = content_is_too_small || self.always_show_scroll;
|
||||||
if show_scroll_this_frame || state.show_scroll {
|
if show_scroll_this_frame || state.show_scroll {
|
||||||
let left = inner_rect.right() + 2.0;
|
let left = inner_rect.right() + 2.0;
|
||||||
let right = outer_rect.right();
|
let right = outer_rect.right();
|
||||||
|
|
|
@ -136,15 +136,26 @@ impl<'open> Window<'open> {
|
||||||
scroll,
|
scroll,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
let movable = area.is_movable();
|
||||||
|
let area = area.movable(false); // We move it manually
|
||||||
|
let resizable = resize.is_resizable();
|
||||||
|
let resize = resize.resizable(false); // We move it manually
|
||||||
|
|
||||||
|
let window_id = Id::new(title_label.text());
|
||||||
|
let area_layer = area.layer();
|
||||||
|
let resize_id = window_id.with("resize");
|
||||||
|
let collapsing_id = window_id.with("collapsing");
|
||||||
|
|
||||||
|
let resize = resize.id(resize_id);
|
||||||
|
|
||||||
if matches!(open, Some(false)) {
|
if matches!(open, Some(false)) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));
|
let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));
|
||||||
|
|
||||||
Some(area.show(ctx, |ui| {
|
let full_interact = area.show(ctx, |ui| {
|
||||||
frame.show(ui, |ui| {
|
frame.show(ui, |ui| {
|
||||||
let collapsing_id = ui.make_child_id("collapsing");
|
|
||||||
let default_expanded = true;
|
let default_expanded = true;
|
||||||
let mut collapsing = collapsing_header::State::from_memory_with_default_open(
|
let mut collapsing = collapsing_header::State::from_memory_with_default_open(
|
||||||
ui,
|
ui,
|
||||||
|
@ -163,6 +174,7 @@ impl<'open> Window<'open> {
|
||||||
.collapsing_headers
|
.collapsing_headers
|
||||||
.insert(collapsing_id, collapsing);
|
.insert(collapsing_id, collapsing);
|
||||||
|
|
||||||
|
// TODO: fix collapsing window animation
|
||||||
let content = collapsing.add_contents(ui, |ui| {
|
let content = collapsing.add_contents(ui, |ui| {
|
||||||
resize.show(ui, |ui| {
|
resize.show(ui, |ui| {
|
||||||
ui.add(Separator::new().line_width(1.0)); // TODO: nicer way to split window title from contents
|
ui.add(Separator::new().line_width(1.0)); // TODO: nicer way to split window title from contents
|
||||||
|
@ -195,10 +207,247 @@ impl<'open> Window<'open> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}))
|
});
|
||||||
|
|
||||||
|
let resizable =
|
||||||
|
resizable && collapsing_header::State::is_open(ctx, collapsing_id).unwrap_or_default();
|
||||||
|
|
||||||
|
if movable || resizable {
|
||||||
|
let possible = PossibleInteractions { movable, resizable };
|
||||||
|
|
||||||
|
// TODO: not when collapsed, and not when resizing or moving is disabled etc
|
||||||
|
let pre_resize = ctx.round_rect_to_pixels(full_interact.rect);
|
||||||
|
let new_rect = resize_window(
|
||||||
|
ctx,
|
||||||
|
possible,
|
||||||
|
area_layer,
|
||||||
|
window_id.with("frame_resize"),
|
||||||
|
pre_resize,
|
||||||
|
);
|
||||||
|
let new_rect = ctx.round_rect_to_pixels(new_rect);
|
||||||
|
if new_rect != pre_resize {
|
||||||
|
let mut area_state = ctx.memory().areas.get(area_layer.id).unwrap();
|
||||||
|
area_state.pos = new_rect.min;
|
||||||
|
ctx.memory().areas.set_state(area_layer, area_state);
|
||||||
|
|
||||||
|
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.size + new_rect.size() - pre_resize.size());
|
||||||
|
ctx.memory().resize.insert(resize_id, resize_state);
|
||||||
|
|
||||||
|
ctx.memory().areas.move_to_top(area_layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(full_interact)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
struct PossibleInteractions {
|
||||||
|
movable: bool,
|
||||||
|
resizable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct FrameInteraction {
|
||||||
|
area_layer: Layer,
|
||||||
|
start_rect: Rect,
|
||||||
|
start_mouse_pos: Pos2,
|
||||||
|
left: bool,
|
||||||
|
right: bool,
|
||||||
|
top: bool,
|
||||||
|
bottom: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameInteraction {
|
||||||
|
pub fn set_cursor(&self, ctx: &Context) {
|
||||||
|
if (self.left && self.top) || (self.right && self.bottom) {
|
||||||
|
ctx.output().cursor_icon = CursorIcon::ResizeNwSe;
|
||||||
|
} else if (self.right && self.top) || (self.left && self.bottom) {
|
||||||
|
ctx.output().cursor_icon = CursorIcon::ResizeNeSw;
|
||||||
|
} else if self.left || self.right {
|
||||||
|
ctx.output().cursor_icon = CursorIcon::ResizeHorizontal;
|
||||||
|
} else if self.bottom || self.top {
|
||||||
|
ctx.output().cursor_icon = CursorIcon::ResizeVertical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_resize(&self) -> bool {
|
||||||
|
self.left || self.right || self.top || self.bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_pure_move(&self) -> bool {
|
||||||
|
!self.is_resize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize_window(
|
||||||
|
ctx: &Context,
|
||||||
|
possible: PossibleInteractions,
|
||||||
|
area_layer: Layer,
|
||||||
|
id: Id,
|
||||||
|
mut rect: Rect,
|
||||||
|
) -> Rect {
|
||||||
|
if let Some(frame_interaction) = frame_interaction(ctx, possible, area_layer, id, rect) {
|
||||||
|
frame_interaction.set_cursor(ctx);
|
||||||
|
if let Some(mouse_pos) = ctx.input().mouse_pos {
|
||||||
|
rect = frame_interaction.start_rect; // prevent drift
|
||||||
|
|
||||||
|
if frame_interaction.is_resize() {
|
||||||
|
if frame_interaction.left {
|
||||||
|
rect.min.x = ctx.round_to_pixel(mouse_pos.x);
|
||||||
|
} else if frame_interaction.right {
|
||||||
|
rect.max.x = ctx.round_to_pixel(mouse_pos.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
if frame_interaction.top {
|
||||||
|
rect.min.y = ctx.round_to_pixel(mouse_pos.y);
|
||||||
|
} else if frame_interaction.bottom {
|
||||||
|
rect.max.y = ctx.round_to_pixel(mouse_pos.y);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// movevement
|
||||||
|
rect = rect.translate(mouse_pos - frame_interaction.start_mouse_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame_interaction(
|
||||||
|
ctx: &Context,
|
||||||
|
possible: PossibleInteractions,
|
||||||
|
area_layer: Layer,
|
||||||
|
id: Id,
|
||||||
|
rect: Rect,
|
||||||
|
) -> Option<FrameInteraction> {
|
||||||
|
{
|
||||||
|
let active_id = ctx.memory().active_id;
|
||||||
|
if active_id.is_none() {
|
||||||
|
let frame_interaction = ctx.memory().frame_interaction;
|
||||||
|
if let Some(frame_interaction) = frame_interaction {
|
||||||
|
if frame_interaction.area_layer == area_layer {
|
||||||
|
eprintln!("Letting go of window");
|
||||||
|
if frame_interaction.is_pure_move() {
|
||||||
|
// Throw window:
|
||||||
|
let mut area_state = ctx.memory().areas.get(area_layer.id).unwrap();
|
||||||
|
area_state.vel = ctx.input().mouse_velocity;
|
||||||
|
eprintln!("Throwing window with velocity {:?}", area_state.vel);
|
||||||
|
ctx.memory().areas.set_state(area_layer, area_state);
|
||||||
|
}
|
||||||
|
ctx.memory().frame_interaction = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if active_id.is_some() && active_id != Some(id) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut frame_interaction = { ctx.memory().frame_interaction.clone() };
|
||||||
|
|
||||||
|
if frame_interaction.is_none() {
|
||||||
|
if let Some(hover_frame_interaction) = resize_hover(ctx, possible, area_layer, rect) {
|
||||||
|
hover_frame_interaction.set_cursor(ctx);
|
||||||
|
if ctx.input().mouse_pressed {
|
||||||
|
ctx.memory().active_id = Some(id);
|
||||||
|
frame_interaction = Some(hover_frame_interaction);
|
||||||
|
ctx.memory().frame_interaction = frame_interaction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(frame_interaction) = frame_interaction {
|
||||||
|
let is_active = ctx.memory().active_id == Some(id);
|
||||||
|
|
||||||
|
if is_active && frame_interaction.area_layer == area_layer {
|
||||||
|
return Some(frame_interaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize_hover(
|
||||||
|
ctx: &Context,
|
||||||
|
possible: PossibleInteractions,
|
||||||
|
area_layer: Layer,
|
||||||
|
rect: Rect,
|
||||||
|
) -> Option<FrameInteraction> {
|
||||||
|
if let Some(mouse_pos) = ctx.input().mouse_pos {
|
||||||
|
if let Some(top_layer) = ctx.memory().layer_at(mouse_pos) {
|
||||||
|
if top_layer != area_layer && top_layer.order != Order::Background {
|
||||||
|
return None; // Another window is on top here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let side_interact_radius = 5.0; // TODO: from style
|
||||||
|
let corner_interact_radius = 10.0; // TODO
|
||||||
|
if rect.expand(side_interact_radius).contains(mouse_pos) {
|
||||||
|
let (mut left, mut right, mut top, mut bottom) = Default::default();
|
||||||
|
if possible.resizable {
|
||||||
|
right = (rect.right() - mouse_pos.x).abs() <= side_interact_radius;
|
||||||
|
bottom = (rect.bottom() - mouse_pos.y).abs() <= side_interact_radius;
|
||||||
|
|
||||||
|
if rect.right_bottom().dist(mouse_pos) < corner_interact_radius {
|
||||||
|
right = true;
|
||||||
|
bottom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if possible.movable {
|
||||||
|
left = (rect.left() - mouse_pos.x).abs() <= side_interact_radius;
|
||||||
|
top = (rect.top() - mouse_pos.y).abs() <= side_interact_radius;
|
||||||
|
|
||||||
|
if rect.right_top().dist(mouse_pos) < corner_interact_radius {
|
||||||
|
right = true;
|
||||||
|
top = true;
|
||||||
|
}
|
||||||
|
if rect.left_top().dist(mouse_pos) < corner_interact_radius {
|
||||||
|
left = true;
|
||||||
|
top = true;
|
||||||
|
}
|
||||||
|
if rect.left_bottom().dist(mouse_pos) < corner_interact_radius {
|
||||||
|
left = true;
|
||||||
|
bottom = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let any_resize = left || right || top || bottom;
|
||||||
|
|
||||||
|
if !any_resize && !possible.movable {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if any_resize || possible.movable {
|
||||||
|
Some(FrameInteraction {
|
||||||
|
area_layer,
|
||||||
|
start_rect: rect,
|
||||||
|
start_mouse_pos: mouse_pos,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
top,
|
||||||
|
bottom,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
fn show_title_bar(
|
fn show_title_bar(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
title_label: Label,
|
title_label: Label,
|
||||||
|
|
|
@ -151,6 +151,13 @@ impl Context {
|
||||||
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
|
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn round_rect_to_pixels(&self, rect: Rect) -> Rect {
|
||||||
|
Rect {
|
||||||
|
min: self.round_pos_to_pixels(rect.min),
|
||||||
|
max: self.round_pos_to_pixels(rect.max),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
pub fn begin_frame(self: &mut Arc<Self>, new_input: RawInput) {
|
pub fn begin_frame(self: &mut Arc<Self>, new_input: RawInput) {
|
||||||
|
@ -173,7 +180,9 @@ impl Context {
|
||||||
if let Some(mouse_pos) = new_raw_input.mouse_pos {
|
if let Some(mouse_pos) = new_raw_input.mouse_pos {
|
||||||
self.mouse_tracker.add(new_raw_input.time, mouse_pos);
|
self.mouse_tracker.add(new_raw_input.time, mouse_pos);
|
||||||
} else {
|
} else {
|
||||||
self.mouse_tracker.clear();
|
// we do not clear the `mouse_tracker` here, because it is exactly when a finger has
|
||||||
|
// released from the touch screen that we may want to assign a velocity to whatever
|
||||||
|
// the user tried to throw
|
||||||
}
|
}
|
||||||
let new_input = GuiInput::from_last_and_new(&self.raw_input, &new_raw_input);
|
let new_input = GuiInput::from_last_and_new(&self.raw_input, &new_raw_input);
|
||||||
self.previus_input = std::mem::replace(&mut self.input, new_input);
|
self.previus_input = std::mem::replace(&mut self.input, new_input);
|
||||||
|
|
|
@ -391,6 +391,18 @@ impl Rect {
|
||||||
Rect::from_min_max(self.min - amnt, self.max + amnt)
|
Rect::from_min_max(self.min - amnt, self.max + amnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shrink by this much in each direction, keeping the center
|
||||||
|
#[must_use]
|
||||||
|
pub fn shrink(self, amnt: f32) -> Self {
|
||||||
|
self.shrink2(Vec2::splat(amnt))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shrink by this much in each direction, keeping the center
|
||||||
|
#[must_use]
|
||||||
|
pub fn shrink2(self, amnt: Vec2) -> Self {
|
||||||
|
Rect::from_min_max(self.min + amnt, self.max - amnt)
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn translate(self, amnt: Vec2) -> Self {
|
pub fn translate(self, amnt: Vec2) -> Self {
|
||||||
Rect::from_min_size(self.min + amnt, self.size())
|
Rect::from_min_size(self.min + amnt, self.size())
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
containers::{area, collapsing_header, menu, resize, scroll_area},
|
containers::{area, collapsing_header, menu, resize, scroll_area, window},
|
||||||
widgets::text_edit,
|
widgets::text_edit,
|
||||||
Id, Layer, Pos2, Rect,
|
Id, Layer, Pos2, Rect,
|
||||||
};
|
};
|
||||||
|
@ -24,6 +24,9 @@ pub struct Memory {
|
||||||
pub(crate) scroll_areas: HashMap<Id, scroll_area::State>,
|
pub(crate) scroll_areas: HashMap<Id, scroll_area::State>,
|
||||||
pub(crate) text_edit: HashMap<Id, text_edit::State>,
|
pub(crate) text_edit: HashMap<Id, text_edit::State>,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub(crate) frame_interaction: Option<window::FrameInteraction>,
|
||||||
|
|
||||||
pub(crate) areas: Areas,
|
pub(crate) areas: Areas,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,10 @@ pub enum CursorIcon {
|
||||||
Default,
|
Default,
|
||||||
/// Pointing hand, used for e.g. web links
|
/// Pointing hand, used for e.g. web links
|
||||||
PointingHand,
|
PointingHand,
|
||||||
|
ResizeHorizontal,
|
||||||
|
ResizeNeSw,
|
||||||
ResizeNwSe,
|
ResizeNwSe,
|
||||||
|
ResizeVertical,
|
||||||
Text,
|
Text,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -164,7 +164,10 @@ pub fn translate_cursor(cursor_icon: emigui::CursorIcon) -> glutin::MouseCursor
|
||||||
match cursor_icon {
|
match cursor_icon {
|
||||||
CursorIcon::Default => glutin::MouseCursor::Default,
|
CursorIcon::Default => glutin::MouseCursor::Default,
|
||||||
CursorIcon::PointingHand => glutin::MouseCursor::Hand,
|
CursorIcon::PointingHand => glutin::MouseCursor::Hand,
|
||||||
|
CursorIcon::ResizeHorizontal => glutin::MouseCursor::EwResize,
|
||||||
|
CursorIcon::ResizeNeSw => glutin::MouseCursor::NeswResize,
|
||||||
CursorIcon::ResizeNwSe => glutin::MouseCursor::NwseResize,
|
CursorIcon::ResizeNwSe => glutin::MouseCursor::NwseResize,
|
||||||
|
CursorIcon::ResizeVertical => glutin::MouseCursor::NsResize,
|
||||||
CursorIcon::Text => glutin::MouseCursor::Text,
|
CursorIcon::Text => glutin::MouseCursor::Text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue