Skip to content

Commit

Permalink
Change Widget::update to mutate the Widget::State rather than return …
Browse files Browse the repository at this point in the history
…a new one. Move input capturing from Widget trait to UiCell. Closes #554.
  • Loading branch information
mitchmindtree committed Oct 19, 2015
1 parent 189c9ab commit 08ceec7
Show file tree
Hide file tree
Showing 15 changed files with 589 additions and 567 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub use position::Matrix as PositionMatrix;
pub use theme::{Align, Theme};
pub use ui::{GlyphCache, Ui, UserInput};
pub use widget::{drag, scroll, CommonBuilder, DrawArgs, UiCell, UpdateArgs, Widget};
pub use widget::CommonState as WidgetCommonState;
pub use widget::Id as WidgetId;
pub use widget::Index as WidgetIndex;
pub use widget::State as WidgetState;
Expand Down
38 changes: 32 additions & 6 deletions src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,24 +678,40 @@ pub fn get_mouse_state<C>(ui: &Ui<C>, idx: widget::Index) -> Option<Mouse> {


/// Indicate that the widget with the given widget::Index has captured the mouse.
pub fn mouse_captured_by<C>(ui: &mut Ui<C>, idx: widget::Index) {
///
/// Returns true if the mouse was successfully captured.
///
/// Returns false if the mouse was already captured.
pub fn mouse_captured_by<C>(ui: &mut Ui<C>, idx: widget::Index) -> bool {
// If the mouse isn't already captured, set idx as the capturing widget.
if let None = ui.maybe_captured_mouse {
ui.maybe_captured_mouse = Some(Capturing::Captured(idx));
return true;
}
false
}


/// Indicate that the widget is no longer capturing the mouse.
pub fn mouse_uncaptured_by<C>(ui: &mut Ui<C>, idx: widget::Index) {
///
/// Returns true if the mouse was sucessfully released.
///
/// Returns false if the mouse wasn't captured by the widget in the first place.
pub fn mouse_uncaptured_by<C>(ui: &mut Ui<C>, idx: widget::Index) -> bool {
// Check that we are indeed the widget that is currently capturing the Mouse before releasing.
if ui.maybe_captured_mouse == Some(Capturing::Captured(idx)) {
ui.maybe_captured_mouse = Some(Capturing::JustReleased)
ui.maybe_captured_mouse = Some(Capturing::JustReleased);
return true;
}
false
}

/// Indicate that the widget with the given widget::Index has captured the keyboard.
pub fn keyboard_captured_by<C>(ui: &mut Ui<C>, idx: widget::Index) {
///
/// Returns true if the keyboard was successfully captured.
///
/// Returns false if the keyboard was already captured by another widget.
pub fn keyboard_captured_by<C>(ui: &mut Ui<C>, idx: widget::Index) -> bool {
match ui.maybe_captured_keyboard {
Some(Capturing::Captured(captured_idx)) => if idx != captured_idx {
writeln!(::std::io::stderr(),
Expand All @@ -707,20 +723,29 @@ pub fn keyboard_captured_by<C>(ui: &mut Ui<C>, idx: widget::Index) {
"Warning: {:?} tried to capture the keyboard, however it was \
already captured.", idx).unwrap();
},
None => ui.maybe_captured_keyboard = Some(Capturing::Captured(idx)),
None => {
ui.maybe_captured_keyboard = Some(Capturing::Captured(idx));
return true;
},
}
false
}


/// Indicate that the widget is no longer capturing the keyboard.
pub fn keyboard_uncaptured_by<C>(ui: &mut Ui<C>, idx: widget::Index) {
///
/// Returns true if the keyboard was successfully released.
///
/// Returns false if the keyboard wasn't captured by the given widget in the first place.
pub fn keyboard_uncaptured_by<C>(ui: &mut Ui<C>, idx: widget::Index) -> bool {
match ui.maybe_captured_keyboard {
Some(Capturing::Captured(captured_idx)) => if idx != captured_idx {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to uncapture the keyboard, however it is \
actually captured by {:?}.", idx, captured_idx).unwrap();
} else {
ui.maybe_captured_keyboard = Some(Capturing::JustReleased);
return true;
},
Some(Capturing::JustReleased) => {
writeln!(::std::io::stderr(),
Expand All @@ -733,6 +758,7 @@ pub fn keyboard_uncaptured_by<C>(ui: &mut Ui<C>, idx: widget::Index) {
was not captured", idx).unwrap();
},
}
false
}


Expand Down
61 changes: 23 additions & 38 deletions src/widget/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,21 +120,6 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() {
}
fn style(&self) -> Style { self.style.clone() }

fn capture_mouse(prev: &State, new: &State) -> bool {
match (prev.interaction, new.interaction) {
(Interaction::Highlighted, Interaction::Clicked) => true,
_ => false,
}
}

fn uncapture_mouse(prev: &State, new: &State) -> bool {
match (prev.interaction, new.interaction) {
(Interaction::Clicked, Interaction::Highlighted) => true,
(Interaction::Clicked, Interaction::Normal) => true,
_ => false,
}
}

fn default_width<C: CharacterCache>(&self, theme: &Theme, _: &GlyphCache<C>) -> Scalar {
const DEFAULT_WIDTH: Scalar = 64.0;
self.common.maybe_width.or(theme.maybe_button.as_ref().map(|default| {
Expand All @@ -150,48 +135,48 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() {
}

/// Update the state of the Button.
fn update<C>(mut self, args: widget::UpdateArgs<Self, C>) -> Option<State>
where C: CharacterCache
{
let widget::UpdateArgs { prev_state, rect, ui, .. } = args;
let widget::State { ref state, .. } = *prev_state;
fn update<C: CharacterCache>(mut self, args: widget::UpdateArgs<Self, C>) {
let widget::UpdateArgs { state, rect, mut ui, .. } = args;
let maybe_mouse = ui.input().maybe_mouse;

// Check whether or not a new interaction has occurred.
let new_interaction = match (self.enabled, maybe_mouse) {
(false, _) | (true, None) => Interaction::Normal,
(true, Some(mouse)) => {
let is_over = rect.is_over(mouse.xy);
get_new_interaction(is_over, state.interaction, mouse)
get_new_interaction(is_over, state.view().interaction, mouse)
},
};

// Capture the mouse if it was clicked, uncpature if it was released.
match (state.view().interaction, new_interaction) {
(Interaction::Highlighted, Interaction::Clicked) => { ui.capture_mouse(); },
(Interaction::Clicked, Interaction::Highlighted) |
(Interaction::Clicked, Interaction::Normal) => { ui.uncapture_mouse(); },
_ => (),
}

// If the mouse was released over button, react.
if let (Interaction::Clicked, Interaction::Highlighted) =
(state.interaction, new_interaction) {
(state.view().interaction, new_interaction) {
if let Some(ref mut react) = self.maybe_react { react() }
}

// A function for constructing a new state.
let new_state = || {
State {
maybe_label: self.maybe_label.as_ref().map(|label| label.to_string()),
interaction: new_interaction,
}
};

// Check whether or not the state has changed since the previous update.
let state_has_changed = state.interaction != new_interaction
|| state.maybe_label.as_ref().map(|string| &string[..]) != self.maybe_label;
// If there has been a change in interaction, set the new one.
if state.view().interaction != new_interaction {
state.update(|state| state.interaction = new_interaction);
}

// Construct the new state if there was a change.
if state_has_changed { Some(new_state()) } else { None }
// If the label has changed, update it.
if state.view().maybe_label.as_ref().map(|label| &label[..]) != self.maybe_label {
state.update(|state| {
state.maybe_label = self.maybe_label.as_ref().map(|label| label.to_string())
});
}
}

/// Construct an Element from the given Button State.
fn draw<C>(args: widget::DrawArgs<Self, C>) -> Element
where C: CharacterCache
{
fn draw<C: CharacterCache>(args: widget::DrawArgs<Self, C>) -> Element {
use elmesque::form::{self, collage, text};

let widget::DrawArgs { state, style, theme, rect, .. } = args;
Expand Down
75 changes: 35 additions & 40 deletions src/widget/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,8 @@ impl<'a> Widget for Canvas<'a> {
}

/// Update the state of the Canvas.
fn update<C>(self, args: widget::UpdateArgs<Self, C>) -> Option<State>
where C: CharacterCache,
{
let widget::UpdateArgs { prev_state, rect, ui, .. } = args;
let widget::State { ref state, .. } = *prev_state;
let State { interaction, time_last_clicked, ref maybe_title_bar } = *state;
fn update<C>(self, args: widget::UpdateArgs<Self, C>) {
let widget::UpdateArgs { state, rect, ui, .. } = args;
let maybe_mouse = ui.input().maybe_mouse;
let title_bar_font_size = self.style.title_bar_font_size(ui.theme());
let maybe_title_bar_rect = if self.show_title_bar {
Expand All @@ -283,55 +279,54 @@ impl<'a> Widget for Canvas<'a> {
// If there is new mouse state, check for a new interaction.
let new_interaction = if let Some(mouse) = maybe_mouse {
let is_over_elem = is_over(rect, maybe_title_bar_rect, mouse.xy);
get_new_interaction(is_over_elem, interaction, mouse)
get_new_interaction(is_over_elem, state.view().interaction, mouse)
} else {
Interaction::Normal
};

// If the canvas was clicked, dragged or released, update the time_last_clicked.
let new_time_last_clicked = match (interaction, new_interaction) {
let new_time_last_clicked = match (state.view().interaction, new_interaction) {
(Interaction::Highlighted(_), Interaction::Clicked(_, _)) |
(Interaction::Clicked(_, _), Interaction::Highlighted(_)) |
(Interaction::Clicked(_, _), Interaction::Clicked(_, _)) => precise_time_ns(),
_ => time_last_clicked,
_ => state.view().time_last_clicked,
};

// A function for constructing a new state.
let new_state = || State {
interaction: new_interaction,
time_last_clicked: new_time_last_clicked,
maybe_title_bar: maybe_title_bar_rect.map(|rect| TitleBar {
maybe_label: self.maybe_title_bar_label.as_ref()
.map(|label| (label.to_string(), title_bar_font_size)),
rect: rect,
}),
if state.view().interaction != new_interaction {
state.update(|state| state.interaction = new_interaction);
}

if state.view().time_last_clicked != new_time_last_clicked {
state.update(|state| state.time_last_clicked = new_time_last_clicked);
}

let title_bar_has_changed = match state.view().maybe_title_bar {
None => self.show_title_bar,
Some(ref title_bar) => {
Some(title_bar.rect) != maybe_title_bar_rect
|| match title_bar.maybe_label {
None => false,
Some((ref label, font_size)) => {
Some(&label[..]) != self.maybe_title_bar_label
|| font_size != title_bar_font_size
},
}
},
};

// Check whether or not the state has changed since the previous update.
let state_has_changed = interaction != new_interaction
|| time_last_clicked != new_time_last_clicked
|| match *maybe_title_bar {
None => self.show_title_bar,
Some(ref title_bar) => {
Some(title_bar.rect) != maybe_title_bar_rect
|| match title_bar.maybe_label {
None => false,
Some((ref label, font_size)) => {
Some(&label[..]) != self.maybe_title_bar_label
|| font_size != title_bar_font_size
},
}
},
};

// Construct the new state if there was a change.
if state_has_changed { Some(new_state()) } else { None }
if title_bar_has_changed {
state.update(|state| {
state.maybe_title_bar = maybe_title_bar_rect.map(|rect| TitleBar {
rect: rect,
maybe_label: self.maybe_title_bar_label.as_ref()
.map(|label| (label.to_string(), title_bar_font_size)),
});
});
}
}

/// Draw the canvas.
fn draw<C>(args: widget::DrawArgs<Self, C>) -> Element
where C: CharacterCache,
{
fn draw<C: CharacterCache>(args: widget::DrawArgs<Self, C>) -> Element {
use elmesque::form::{self, collage, text};

let widget::DrawArgs { rect, state, style, theme, glyph_cache, .. } = args;
Expand Down
Loading

0 comments on commit 08ceec7

Please sign in to comment.