Skip to content

Commit

Permalink
Changelog and cleanup
Browse files Browse the repository at this point in the history
- Make invalidate_text_field not take an Option
- Use WidgetWrapper for Padding
- remove some unused code
- Other small doc fixes
- Add TextBox example to web examples
  • Loading branch information
cmyr committed Mar 10, 2021
1 parent df6ec41 commit e83d3ce
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 68 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ You can find its changes [documented below](#070---2021-01-01).
# Unreleased

### Highlights
- International text input support (IME) on macOS.

### Added
- Add `scroll()` method in WidgetExt ([#1600] by [@totsteps])
Expand All @@ -22,6 +23,7 @@ You can find its changes [documented below](#070---2021-01-01).
- Shell: windows implementation from content_insets ([#1592] by [@HoNile])
- Shell: IME API and macOS IME implementation ([#1619] by [@lord])
- Scroll::content_must_fill and a few other new Scroll methods ([#1635] by [@cmyr])
- New `TextBox` widget with IME integration ([#1636] by [@cmyr])

### Changed

Expand Down Expand Up @@ -632,6 +634,7 @@ Last release without a changelog :(
[#1619]: https://github.com/linebender/druid/pull/1619
[#1634]: https://github.com/linebender/druid/pull/1634
[#1635]: https://github.com/linebender/druid/pull/1635
[#1636]: https://github.com/linebender/druid/pull/1636

[Unreleased]: https://github.com/linebender/druid/compare/v0.7.0...master
[0.7.0]: https://github.com/linebender/druid/compare/v0.6.0...v0.7.0
Expand Down
1 change: 1 addition & 0 deletions druid/examples/web/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ impl_example!(styled_text.unwrap());
impl_example!(switches);
impl_example!(timer);
impl_example!(tabs);
impl_example!(textbox);
impl_example!(transparency);
impl_example!(view_switcher);
impl_example!(widget_gallery);
Expand Down
5 changes: 3 additions & 2 deletions druid/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use std::{
use crate::core::{CommandQueue, CursorChange, FocusChange, WidgetState};
use crate::env::KeyLike;
use crate::piet::{Piet, PietText, RenderContext};
use crate::shell::text::Event as ImeInvalidation;
use crate::shell::Region;
use crate::text::{ImeHandlerRef, TextFieldRegistration};
use crate::{
Expand Down Expand Up @@ -379,10 +380,10 @@ impl_context_method!(EventCtx<'_, '_>, UpdateCtx<'_, '_>, LifeCycleCtx<'_, '_>,
/// A widget that accepts text input should call this anytime input state
/// (such as the text or the selection) changes as a result of a non text-input
/// event.
pub fn invalidate_text_input(&mut self, event: Option<crate::shell::text::Event>) {
pub fn invalidate_text_input(&mut self, event: ImeInvalidation) {
let payload = commands::ImeInvalidation {
widget: self.widget_id(),
event: event.unwrap_or(crate::shell::text::Event::Reset),
event,
};
let cmd = commands::INVALIDATE_IME
.with(payload)
Expand Down
14 changes: 9 additions & 5 deletions druid/src/text/input_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ pub struct TextComponent<T> {
lock: Arc<Cell<ImeLock>>,
}

/// The inner state of an `EditSession`.
/// Editable text state.
///
/// This may be modified directly, or it may be modified by the platform.
/// This is the inner state of a [`TextComponent`]. It should only be accessed
/// through its containing [`TextComponent`], or by the platform through an
/// [`ImeHandlerRef`] created by [`TextComponent::input_handler`].
#[derive(Debug, Clone)]
pub struct EditSession<T> {
/// The inner [`TextLayout`] object.
Expand Down Expand Up @@ -147,8 +149,10 @@ impl<T: TextStorage + EditableText> ImeHandlerRef for EditSessionRef<T> {
}

impl TextComponent<()> {
/// If the payload is true, this follows an edit, and the view will need to be laid
/// out before scrolling.
/// A notification sent by the component when the cursor has moved.
///
/// If the payload is true, this follows an edit, and the view will need
/// layout before scrolling.
pub const SCROLL_TO: Selector<bool> = Selector::new("druid-builtin.textbox-scroll-to");

/// A notification sent by the component when the user hits return.
Expand Down Expand Up @@ -322,7 +326,7 @@ impl<T: TextStorage + EditableText> Widget<T> for TextComponent<T> {
let new_origin = ctx.window_origin();
if prev_origin != new_origin {
self.borrow_mut().origin = ctx.window_origin();
ctx.invalidate_text_input(Some(ImeUpdate::LayoutChanged));
ctx.invalidate_text_input(ImeUpdate::LayoutChanged);
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion druid/src/text/movement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub enum Movement {
EndOfDocument,
}

//FIXME: we should remove this whole file, and use the Movement type defined in druid-shell?
impl From<crate::shell::text::Movement> for Movement {
fn from(src: crate::shell::text::Movement) -> Movement {
use crate::shell::text::{Direction, Movement as SMovemement, VerticalMovement};
Expand All @@ -63,7 +64,8 @@ impl From<crate::shell::text::Movement> for Movement {
| SMovemement::Vertical(VerticalMovement::PageDown) => Movement::Down,
SMovemement::Vertical(VerticalMovement::DocumentStart) => Movement::StartOfDocument,
SMovemement::Vertical(VerticalMovement::DocumentEnd) => Movement::EndOfDocument,
_ => unreachable!(),
// the source enum is non_exhaustive
_ => panic!("unhandled movement {:?}", src),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions druid/src/text/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ impl Selection {
}
}

//FIXME: delete this file, unify with druid-shell::text::Selection
impl From<Selection> for crate::shell::text::Selection {
fn from(src: Selection) -> crate::shell::text::Selection {
crate::shell::text::Selection {
Expand Down
78 changes: 23 additions & 55 deletions druid/src/widget/textbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::kurbo::Insets;
use crate::piet::TextLayout as _;
use crate::text::{EditableText, Selection, TextComponent, TextLayout, TextStorage};
use crate::widget::prelude::*;
use crate::widget::{Padding, Scroll};
use crate::widget::{Padding, Scroll, WidgetWrapper};
use crate::{
theme, Color, FontDescriptor, KeyOrValue, Point, Rect, TextAlignment, TimerToken, Vec2,
};
Expand Down Expand Up @@ -48,7 +48,6 @@ pub struct TextBox<T> {
inner: Padding<T, Scroll<T, TextComponent<T>>>,
scroll_to_selection_after_layout: bool,
multiline: bool,
wrap_lines: bool,
/// true if a click event caused us to gain focus.
///
/// On macOS, if focus happens via click then we set the selection based
Expand Down Expand Up @@ -77,7 +76,6 @@ impl<T: EditableText + TextStorage> TextBox<T> {
scroll_to_selection_after_layout: false,
placeholder,
multiline: false,
wrap_lines: false,
was_focused_from_click: false,
cursor_on: false,
cursor_timer: TimerToken::INVALID,
Expand All @@ -90,10 +88,12 @@ impl<T: EditableText + TextStorage> TextBox<T> {
pub fn multiline() -> Self {
let mut this = TextBox::new();
this.inner
.child_mut()
.wrapped_mut()
.set_enabled_scrollbars(crate::scroll_component::ScrollbarsEnabled::Both);
this.text_mut().borrow_mut().set_accepts_newlines(true);
this.inner.child_mut().set_horizontal_scroll_enabled(false);
this.inner
.wrapped_mut()
.set_horizontal_scroll_enabled(false);
this.multiline = true;
this
}
Expand All @@ -106,9 +106,8 @@ impl<T: EditableText + TextStorage> TextBox<T> {
///
/// [`multiline`]: TextBox::multiline
pub fn with_line_wrapping(mut self, wrap_lines: bool) -> Self {
self.wrap_lines = wrap_lines;
self.inner
.child_mut()
.wrapped_mut()
.set_horizontal_scroll_enabled(!wrap_lines);
self
}
Expand Down Expand Up @@ -275,40 +274,20 @@ impl<T> TextBox<T> {
}

impl<T> TextBox<T> {
///// Set the textbox's selection.
//pub fn set_selection(&mut self, selection: Selection) {
//self.editor.set_selection(selection);
//}

///// Set the text and force the editor to update.
/////
///// This should be rarely needed; the main use-case would be if you need
///// to manually set the text and then immediately do hit-testing or other
///// tasks that rely on having an up-to-date text layout.
//pub fn force_rebuild(&mut self, text: T, factory: &mut PietText, env: &Env) {
//self.editor.set_text(text);
//self.editor.rebuild_if_needed(factory, env);
//}
}

impl<T> TextBox<T> {
//FIXME: maybe a more restrictive API? some kind of `with_text` method that
//takes a closure and makes sure we're locked, and then also notifies the platform
//of changes afterwards?
/// An immutable reference to the inner [`TextComponent`].
///
/// Using this correctly is difficult; please see the [`TextComponent`]
/// docs for more information.
pub fn text(&self) -> &TextComponent<T> {
self.inner.child().child()
self.inner.wrapped().child()
}

/// An immutable reference to the inner [`TextComponent`].
///
/// Using this correctly is difficult; please see the [`TextComponent`]
/// docs for more information.
pub fn text_mut(&mut self) -> &mut TextComponent<T> {
self.inner.child_mut().child_mut()
self.inner.wrapped_mut().child_mut()
}

fn reset_cursor_blink(&mut self, token: TimerToken) {
Expand All @@ -327,22 +306,11 @@ impl<T> TextBox<T> {

impl<T: TextStorage + EditableText> TextBox<T> {
fn rect_for_selection_end(&self) -> Rect {
let selection_end = self.text().borrow().selection().end;
let hit = self
.text()
.borrow()
.layout
.layout()
.unwrap()
.hit_test_text_position(selection_end);
let line = self
.text()
.borrow()
.layout
.layout()
.unwrap()
.line_metric(hit.line)
.unwrap();
let text = self.text().borrow();
let layout = text.layout.layout().unwrap();

let hit = layout.hit_test_text_position(text.selection().end);
let line = layout.line_metric(hit.line).unwrap();
let y0 = line.y_offset;
let y1 = y0 + line.height;
let x = hit.point.x;
Expand All @@ -352,11 +320,11 @@ impl<T: TextStorage + EditableText> TextBox<T> {

fn scroll_to_selection_end(&mut self) {
let rect = self.rect_for_selection_end();
let view_rect = self.inner.child().viewport_rect();
let view_rect = self.inner.wrapped().viewport_rect();
let is_visible =
view_rect.contains(rect.origin()) && view_rect.contains(Point::new(rect.x1, rect.y1));
if !is_visible {
self.inner.child_mut().scroll_to(rect + SCROLL_TO_INSETS);
self.inner.wrapped_mut().scroll_to(rect + SCROLL_TO_INSETS);
}
}
}
Expand Down Expand Up @@ -423,7 +391,7 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {
{
if self.text().borrow().set_clipboard() {
let inval = self.text_mut().borrow_mut().insert_text(data, "");
ctx.invalidate_text_input(Some(inval));
ctx.invalidate_text_input(inval);
}
ctx.set_handled();
}
Expand All @@ -436,7 +404,7 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {
};
if !text.is_empty() {
let inval = self.text_mut().borrow_mut().insert_text(data, text);
ctx.invalidate_text_input(Some(inval));
ctx.invalidate_text_input(inval);
}
}
}
Expand All @@ -456,7 +424,7 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {
if self.text().can_write() && !self.multiline && !self.was_focused_from_click {
let selection = Selection::new(0, data.len());
let _ = self.text_mut().borrow_mut().set_selection(selection);
ctx.invalidate_text_input(Some(druid_shell::text::Event::SelectionChanged));
ctx.invalidate_text_input(druid_shell::text::Event::SelectionChanged);
}
self.reset_cursor_blink(ctx.request_timer(CURSOR_BLINK_DURATION));
self.was_focused_from_click = false;
Expand All @@ -467,7 +435,7 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {
let selection = self.text().borrow().selection();
let selection = Selection::new(selection.end, selection.end);
let _ = self.text_mut().borrow_mut().set_selection(selection);
ctx.invalidate_text_input(Some(druid_shell::text::Event::SelectionChanged));
ctx.invalidate_text_input(druid_shell::text::Event::SelectionChanged);
}
self.cursor_timer = TimerToken::INVALID;
self.was_focused_from_click = false;
Expand All @@ -486,7 +454,7 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {
if self.text().can_write() {
if let Some(ime_invalidation) = self.text_mut().borrow_mut().pending_ime_invalidation()
{
ctx.invalidate_text_input(Some(ime_invalidation));
ctx.invalidate_text_input(ime_invalidation);
}
}
}
Expand All @@ -511,8 +479,8 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {

let layout_baseline = text_metrics.size.height - text_metrics.first_baseline;
let baseline_off = layout_baseline
- (self.inner.child().child_size().height
- self.inner.child().viewport_rect().height())
- (self.inner.wrapped().child_size().height
- self.inner.wrapped().viewport_rect().height())
+ TEXTBOX_INSETS.y1;
ctx.set_baseline_offset(baseline_off);
if self.scroll_to_selection_after_layout {
Expand Down Expand Up @@ -575,7 +543,7 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {
let cursor = if data.is_empty() {
cursor_line + padding_offset
} else {
cursor_line + padding_offset - self.inner.child().offset()
cursor_line + padding_offset - self.inner.wrapped().offset()
};
ctx.stroke(cursor, &cursor_color, 1.);
}
Expand Down
7 changes: 3 additions & 4 deletions druid/src/widget/value_textbox.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018 The Druid Authors.
// Copyright 2021 The Druid Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -167,7 +167,7 @@ impl<T: Data> ValueTextBox<T> {
.borrow_mut()
.set_selection(Selection::new(0, self.buffer.len()))
{
ctx.invalidate_text_input(Some(inval));
ctx.invalidate_text_input(inval);
}
}
self.send_event(ctx, TextBoxEvent::Invalid(err));
Expand Down Expand Up @@ -297,7 +297,6 @@ impl<T: Data + std::fmt::Debug> Widget<T> for ValueTextBox<T> {
self.force_selection = new_sel;

if self.update_data_while_editing && !validation.is_err() {
//FIXME: notify platform of text change
if let Ok(new_data) = self.formatter.value(&self.buffer) {
*data = new_data;
self.last_known_data = Some(data.clone());
Expand Down Expand Up @@ -343,7 +342,7 @@ impl<T: Data + std::fmt::Debug> Widget<T> for ValueTextBox<T> {
if let Some(sel) = self.force_selection.take() {
if self.inner.text().can_write() {
if let Some(change) = self.inner.text_mut().borrow_mut().set_selection(sel) {
ctx.invalidate_text_input(Some(change));
ctx.invalidate_text_input(change);
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion druid/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,9 @@ impl<T: Data> Window<T> {
self.focus = new;
// check if the newly focused widget has an IME session, and
// notify the system if so.
//FIXME: this is quadratic. do we care?
//
// If you're here because a profiler sent you: I guess I should've
// used a hashmap?
let old_was_ime = old
.map(|old| {
self.ime_handlers
Expand Down

0 comments on commit e83d3ce

Please sign in to comment.