Save reboot checkpoint with source layout WIP and native evidence
This commit is contained in:
@@ -325,50 +325,73 @@ impl State {
|
||||
) -> Option<UiElement<'a, Event, iced::Theme, R>> {
|
||||
let d = self.editing.as_ref()?;
|
||||
let event = move |edit| Event::Edit(d.token, edit);
|
||||
let kinds = d.scope.kinds(d.layer);
|
||||
let determined = kinds.len() == 1 && d.kind == kinds.first().copied();
|
||||
let mut fields = column![
|
||||
text(format!("New source · {}", d.layer)).size(18),
|
||||
text(match d.scope {
|
||||
Scope::Lifeline { owner, viewpoint } =>
|
||||
format!("For a lifeline in {} · {:?}", p.named(owner), viewpoint),
|
||||
Scope::Work { owner } => format!("For a contribution in {}", p.named(owner)),
|
||||
Scope::Machine { .. } => "Owner for the pending state machine".into(),
|
||||
text(if determined {
|
||||
format!("New {} · {}", kinds[0], d.layer)
|
||||
} else {
|
||||
format!("New source · {}", d.layer)
|
||||
})
|
||||
.size(12),
|
||||
text("The parent form is retained. This source is created only with its consumer.")
|
||||
.size(12)
|
||||
]
|
||||
.spacing(8);
|
||||
for kind in d.scope.kinds(d.layer) {
|
||||
fields = fields.push(
|
||||
button(text(kind.to_string()))
|
||||
.on_press(event(Edit::Kind(kind)))
|
||||
.style(if d.kind == Some(kind) {
|
||||
button::primary
|
||||
} else {
|
||||
button::secondary
|
||||
}),
|
||||
);
|
||||
}
|
||||
fields = fields.push(
|
||||
.size(14),
|
||||
text_input("Source name (independent of the consumer name)", &d.name)
|
||||
.id(NAME_INPUT)
|
||||
.on_input(move |v| event(Edit::Name(v)))
|
||||
.padding(8),
|
||||
);
|
||||
.size(14)
|
||||
.padding(6),
|
||||
]
|
||||
.spacing(4);
|
||||
if !determined {
|
||||
let mut choices = row![].spacing(6);
|
||||
for kind in kinds {
|
||||
choices = choices.push(
|
||||
container(
|
||||
button(text(kind.to_string()).size(14))
|
||||
.padding(4)
|
||||
.on_press(event(Edit::Kind(kind)))
|
||||
.style(if d.kind == Some(kind) {
|
||||
button::primary
|
||||
} else {
|
||||
button::secondary
|
||||
}),
|
||||
)
|
||||
.id(format!("behavior-source-kind-{kind:?}")),
|
||||
);
|
||||
}
|
||||
fields = fields.push(choices.wrap());
|
||||
}
|
||||
fields = fields.push(
|
||||
text(format!(
|
||||
"Semantic parent: {}",
|
||||
d.parent
|
||||
.as_ref()
|
||||
.map_or(format!("{} layer root", d.layer), |e| format!(
|
||||
"{} · {} · @{}",
|
||||
e.name,
|
||||
e.kind(),
|
||||
e.alias
|
||||
))
|
||||
))
|
||||
.size(12),
|
||||
container(
|
||||
text(format!(
|
||||
"Semantic parent: {}",
|
||||
d.parent
|
||||
.as_ref()
|
||||
.map_or(format!("{} layer root", d.layer), |e| format!(
|
||||
"{} · {} · @{}",
|
||||
e.name,
|
||||
e.kind(),
|
||||
e.alias
|
||||
))
|
||||
))
|
||||
.size(12),
|
||||
)
|
||||
.id("behavior-source-parent"),
|
||||
);
|
||||
fields = fields
|
||||
.push(
|
||||
text(match d.scope {
|
||||
Scope::Lifeline { owner, viewpoint } => {
|
||||
format!("For a lifeline in {} · {:?}", p.named(owner), viewpoint)
|
||||
}
|
||||
Scope::Work { owner } => format!("For a contribution in {}", p.named(owner)),
|
||||
Scope::Machine { .. } => "Owner for the pending state machine".into(),
|
||||
})
|
||||
.size(12),
|
||||
)
|
||||
.push(
|
||||
text("The parent form is retained. This source is created only with its consumer.")
|
||||
.size(12),
|
||||
);
|
||||
if let Some(kind) = d.kind {
|
||||
fields = fields.push(button("Use layer root").on_press_maybe(
|
||||
validation::can_parent(p, kind, d.layer, None).then(|| event(Edit::Parent(None))),
|
||||
@@ -486,3 +509,517 @@ impl Draft {
|
||||
Ok(command)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, debug_assertions))]
|
||||
mod layout_tests {
|
||||
use super::*;
|
||||
use iced::advanced::{Layout, Shell, graphics, layout, renderer, text as shaped, widget};
|
||||
use iced::{Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, Vector};
|
||||
|
||||
// Use Iced's real paragraph shaping/measurement without a window or GPU.
|
||||
// The unit renderer's zero-size paragraphs cannot detect this regression.
|
||||
struct FontLayout;
|
||||
impl renderer::Renderer for FontLayout {
|
||||
fn start_layer(&mut self, _: Rectangle) {}
|
||||
fn end_layer(&mut self) {}
|
||||
fn start_transformation(&mut self, _: Transformation) {}
|
||||
fn end_transformation(&mut self) {}
|
||||
fn reset(&mut self, _: Rectangle) {}
|
||||
fn fill_quad(&mut self, _: renderer::Quad, _: impl Into<Background>) {}
|
||||
fn allocate_image(
|
||||
&mut self,
|
||||
handle: &iced::advanced::image::Handle,
|
||||
callback: impl FnOnce(
|
||||
Result<iced::advanced::image::Allocation, iced::advanced::image::Error>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
renderer::Renderer::allocate_image(&mut (), handle, callback);
|
||||
}
|
||||
}
|
||||
impl shaped::Renderer for FontLayout {
|
||||
type Font = Font;
|
||||
type Paragraph = graphics::text::Paragraph;
|
||||
type Editor = ();
|
||||
const ICON_FONT: Font = Font::DEFAULT;
|
||||
const CHECKMARK_ICON: char = '0';
|
||||
const ARROW_DOWN_ICON: char = '0';
|
||||
const SCROLL_UP_ICON: char = '0';
|
||||
const SCROLL_DOWN_ICON: char = '0';
|
||||
const SCROLL_LEFT_ICON: char = '0';
|
||||
const SCROLL_RIGHT_ICON: char = '0';
|
||||
const ICED_LOGO: char = '0';
|
||||
fn default_font(&self) -> Font {
|
||||
Font::DEFAULT
|
||||
}
|
||||
fn default_size(&self) -> Pixels {
|
||||
Pixels(16.)
|
||||
}
|
||||
fn fill_paragraph(&mut self, _: &Self::Paragraph, _: Point, _: Color, _: Rectangle) {}
|
||||
fn fill_editor(&mut self, _: &Self::Editor, _: Point, _: Color, _: Rectangle) {}
|
||||
fn fill_text(&mut self, _: shaped::Text, _: Point, _: Color, _: Rectangle) {}
|
||||
}
|
||||
#[derive(Default)]
|
||||
struct Geometry {
|
||||
fields: Option<Rectangle>,
|
||||
name: Option<Rectangle>,
|
||||
parent: Option<Rectangle>,
|
||||
actions: Option<Rectangle>,
|
||||
kinds: BTreeMap<String, Rectangle>,
|
||||
text: Vec<(String, Rectangle)>,
|
||||
focus_name: bool,
|
||||
}
|
||||
impl widget::Operation for Geometry {
|
||||
fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn widget::Operation)) {
|
||||
visit(self);
|
||||
}
|
||||
fn container(&mut self, id: Option<&widget::Id>, bounds: Rectangle) {
|
||||
if id == Some(&widget::Id::new("behavior-source-actions")) {
|
||||
self.actions = Some(bounds);
|
||||
}
|
||||
if id == Some(&widget::Id::new("behavior-source-parent")) {
|
||||
self.parent = Some(bounds);
|
||||
}
|
||||
for kind in [
|
||||
Kind::Entity,
|
||||
Kind::Actor,
|
||||
Kind::Role,
|
||||
Kind::System,
|
||||
Kind::SystemActor,
|
||||
Kind::Activity,
|
||||
Kind::Function,
|
||||
] {
|
||||
if id == Some(&widget::Id::from(format!("behavior-source-kind-{kind:?}"))) {
|
||||
self.kinds.insert(kind.to_string(), bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
_: Rectangle,
|
||||
offset: Vector,
|
||||
_: &mut dyn widget::operation::Scrollable,
|
||||
) {
|
||||
if id == Some(&widget::Id::new("behavior-source-fields")) {
|
||||
assert_eq!(
|
||||
offset,
|
||||
Vector::ZERO,
|
||||
"Initial fields must be visible without scrolling"
|
||||
);
|
||||
self.fields = Some(bounds);
|
||||
}
|
||||
}
|
||||
fn text_input(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
_: &mut dyn widget::operation::TextInput,
|
||||
) {
|
||||
if id == Some(&widget::Id::new(NAME_INPUT)) {
|
||||
self.name = Some(bounds);
|
||||
}
|
||||
}
|
||||
fn focusable(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
_: Rectangle,
|
||||
state: &mut dyn widget::operation::Focusable,
|
||||
) {
|
||||
if self.focus_name && id == Some(&widget::Id::new(NAME_INPUT)) {
|
||||
state.focus();
|
||||
}
|
||||
}
|
||||
fn text(&mut self, _: Option<&widget::Id>, bounds: Rectangle, text: &str) {
|
||||
self.text.push((text.into(), bounds));
|
||||
}
|
||||
}
|
||||
fn contains(outer: Rectangle, inner: Rectangle) -> bool {
|
||||
inner.width > 0.
|
||||
&& inner.height > 0.
|
||||
&& inner.x >= outer.x - 0.1
|
||||
&& inner.y >= outer.y - 0.1
|
||||
&& inner.x + inner.width <= outer.x + outer.width + 0.1
|
||||
&& inner.y + inner.height <= outer.y + outer.height + 0.1
|
||||
}
|
||||
fn host<'a>(
|
||||
state: &'a State,
|
||||
p: &'a Project,
|
||||
caption: &str,
|
||||
) -> UiElement<'a, Event, iced::Theme, FontLayout> {
|
||||
iced::widget::column![text(caption.to_owned()).size(12), state.view(p).unwrap()]
|
||||
.spacing(6)
|
||||
.height(Fill)
|
||||
.width(Fill)
|
||||
.into()
|
||||
}
|
||||
fn geometry(
|
||||
view: &mut UiElement<'_, Event, iced::Theme, FontLayout>,
|
||||
tree: &mut widget::Tree,
|
||||
size: Size,
|
||||
) -> (layout::Node, Geometry) {
|
||||
let node =
|
||||
view.as_widget_mut()
|
||||
.layout(tree, &FontLayout, &layout::Limits::new(Size::ZERO, size));
|
||||
let mut geometry = Geometry::default();
|
||||
view.as_widget_mut()
|
||||
.operate(tree, Layout::new(&node), &FontLayout, &mut geometry);
|
||||
(node, geometry)
|
||||
}
|
||||
fn fixture(layer: Layer, viewpoint: Option<ScenarioViewpoint>, work: bool) -> (Project, State) {
|
||||
let mut p = Project::empty();
|
||||
let scope = if work {
|
||||
let owner = p
|
||||
.add_element(
|
||||
if layer == Layer::Oa {
|
||||
Kind::Process
|
||||
} else {
|
||||
Kind::FunctionalChain
|
||||
},
|
||||
layer,
|
||||
"Consumer",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
Scope::Work { owner }
|
||||
} else if let Some(viewpoint) = viewpoint {
|
||||
let owner = p
|
||||
.add_element(
|
||||
Kind::Scenario,
|
||||
layer,
|
||||
"Incident coordination with a long contextual heading 雨 Ω",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
if let Some(SharedBody::Scenario { viewpoint: v, .. }) =
|
||||
p.elements.get_mut(&owner).unwrap().shared_mut()
|
||||
{
|
||||
*v = viewpoint;
|
||||
}
|
||||
Scope::Lifeline { owner, viewpoint }
|
||||
} else {
|
||||
Scope::Machine { layer }
|
||||
};
|
||||
let mut state = State::default();
|
||||
let invocation = Id::new_v4();
|
||||
state
|
||||
.update(&p, scope, invocation, Event::Open(invocation))
|
||||
.unwrap();
|
||||
(p, state)
|
||||
}
|
||||
#[test]
|
||||
fn contextual_source_layout_initial_essentials_fit_short_composed_host_with_real_paragraphs() {
|
||||
for (layer, viewpoint, work) in [
|
||||
(
|
||||
Layer::Oa,
|
||||
Some(ScenarioViewpoint::OperationalActivity),
|
||||
false,
|
||||
),
|
||||
(Layer::Sa, Some(ScenarioViewpoint::Functional), false),
|
||||
(
|
||||
Layer::Oa,
|
||||
Some(ScenarioViewpoint::ParticipantInteraction),
|
||||
false,
|
||||
),
|
||||
(Layer::Sa, Some(ScenarioViewpoint::BoundaryInterface), false),
|
||||
(Layer::Oa, None, false),
|
||||
(Layer::Sa, None, false),
|
||||
(Layer::Oa, None, true),
|
||||
(Layer::Sa, None, true),
|
||||
] {
|
||||
let (p, state) = fixture(layer, viewpoint, work);
|
||||
let raw = serde_json::to_value(&state).unwrap();
|
||||
for width in [340., 500.] {
|
||||
// The retained caption + 6px gap leaves approximately the observed 194px card.
|
||||
let size = Size::new(width, 215.);
|
||||
let mut view = host(&state, &p, "Consumer name: Assessment lifeline");
|
||||
let mut tree = widget::Tree::new(view.as_widget());
|
||||
let (_, g) = geometry(&mut view, &mut tree, size);
|
||||
let fields = g.fields.unwrap();
|
||||
assert!(
|
||||
(125. ..=132.).contains(&fields.height),
|
||||
"Measured field viewport: {fields:?}"
|
||||
);
|
||||
assert!(
|
||||
contains(fields, g.name.unwrap()),
|
||||
"Name clipped: {layer:?}/{viewpoint:?}/{work}/{width}"
|
||||
);
|
||||
assert!(
|
||||
contains(fields, g.parent.unwrap()),
|
||||
"Parent clipped: {layer:?}/{viewpoint:?}/{work}/{width}"
|
||||
);
|
||||
assert!(contains(
|
||||
Rectangle::new(Point::ORIGIN, size),
|
||||
g.actions.unwrap()
|
||||
));
|
||||
assert!(fields.y + fields.height <= g.actions.unwrap().y);
|
||||
let expected = state.editing.as_ref().unwrap().scope.kinds(layer);
|
||||
assert_eq!(
|
||||
g.kinds.len(),
|
||||
if expected.len() == 1 {
|
||||
0
|
||||
} else {
|
||||
expected.len()
|
||||
}
|
||||
);
|
||||
for bounds in g.kinds.values() {
|
||||
assert!(
|
||||
contains(fields, *bounds),
|
||||
"Kind clipped: {bounds:?} in {fields:?}"
|
||||
);
|
||||
}
|
||||
let parent_text = g
|
||||
.text
|
||||
.iter()
|
||||
.find(|(s, _)| s.starts_with("Semantic parent:"))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
parent_text.0,
|
||||
format!("Semantic parent: {layer} layer root")
|
||||
);
|
||||
assert!(
|
||||
parent_text.1.height > 0.,
|
||||
"Paragraph measurement must not be null"
|
||||
);
|
||||
let explanation = g
|
||||
.text
|
||||
.iter()
|
||||
.find(|(s, _)| s.starts_with("The parent form"))
|
||||
.unwrap();
|
||||
assert!(explanation.1.y >= g.parent.unwrap().y + g.parent.unwrap().height);
|
||||
}
|
||||
assert_eq!(
|
||||
serde_json::to_value(&state).unwrap(),
|
||||
raw,
|
||||
"Layout must not choose a kind or mutate the draft"
|
||||
);
|
||||
if state.editing.as_ref().unwrap().scope.kinds(layer).len() == 1 {
|
||||
let mut incomplete = state.clone();
|
||||
incomplete.editing.as_mut().unwrap().kind = None;
|
||||
let mut view = host(&incomplete, &p, "Consumer name: Assessment lifeline");
|
||||
let mut tree = widget::Tree::new(view.as_widget());
|
||||
let (_, g) = geometry(&mut view, &mut tree, Size::new(340., 215.));
|
||||
assert_eq!(
|
||||
g.kinds.len(),
|
||||
1,
|
||||
"An incomplete recovery still needs an explicit repair choice"
|
||||
);
|
||||
assert!(contains(
|
||||
g.fields.unwrap(),
|
||||
*g.kinds.values().next().unwrap()
|
||||
));
|
||||
assert!(contains(g.fields.unwrap(), g.name.unwrap()));
|
||||
assert_eq!(incomplete.editing.as_ref().unwrap().kind, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn contextual_source_layout_long_parent_wraps_after_visible_name_without_losing_raw_data() {
|
||||
let (mut p, mut state) = fixture(Layer::Oa, None, true);
|
||||
let parent = p
|
||||
.add_element(
|
||||
Kind::Activity,
|
||||
Layer::Oa,
|
||||
"Northern operations coordination — exact parent 雨",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let d = state.editing.as_ref().unwrap();
|
||||
let token = d.token;
|
||||
let scope = d.scope;
|
||||
let invocation = Id::new_v4();
|
||||
state
|
||||
.update(
|
||||
&p,
|
||||
scope,
|
||||
invocation,
|
||||
Event::Edit(token, Edit::Parent(Some(parent))),
|
||||
)
|
||||
.unwrap();
|
||||
state
|
||||
.update(
|
||||
&p,
|
||||
scope,
|
||||
invocation,
|
||||
Event::Edit(token, Edit::Name(" Distinct source 雨 ".into())),
|
||||
)
|
||||
.unwrap();
|
||||
let raw = serde_json::to_value(&state).unwrap();
|
||||
for width in [340., 500.] {
|
||||
let size = Size::new(width, 235.);
|
||||
let mut view = host(
|
||||
&state,
|
||||
&p,
|
||||
"Consumer name: Coordination with a long independent label 雨 Ω",
|
||||
);
|
||||
let mut tree = widget::Tree::new(view.as_widget());
|
||||
let (_, g) = geometry(&mut view, &mut tree, size);
|
||||
assert!(contains(g.fields.unwrap(), g.name.unwrap()));
|
||||
assert!(contains(g.fields.unwrap(), g.parent.unwrap()));
|
||||
assert!(contains(
|
||||
Rectangle::new(Point::ORIGIN, size),
|
||||
g.actions.unwrap()
|
||||
));
|
||||
let summary = g
|
||||
.text
|
||||
.iter()
|
||||
.find(|(s, _)| s.starts_with("Semantic parent:"))
|
||||
.unwrap();
|
||||
assert!(summary.0.contains(&p.elements[&parent].name));
|
||||
assert!(summary.0.contains(&p.elements[&parent].alias));
|
||||
assert!(
|
||||
summary.1.height > 15.,
|
||||
"Long parent should exercise real wrapping"
|
||||
);
|
||||
}
|
||||
assert_eq!(serde_json::to_value(&state).unwrap(), raw);
|
||||
assert_eq!(
|
||||
state.editing.as_ref().unwrap().name,
|
||||
" Distinct source 雨 "
|
||||
);
|
||||
|
||||
let (mut p, mut state) = fixture(
|
||||
Layer::Oa,
|
||||
Some(ScenarioViewpoint::ParticipantInteraction),
|
||||
false,
|
||||
);
|
||||
let parent = p
|
||||
.add_element(
|
||||
Kind::Entity,
|
||||
Layer::Oa,
|
||||
&"Northern operational responsibility 雨 ".repeat(8),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let d = state.editing.as_ref().unwrap();
|
||||
let token = d.token;
|
||||
let scope = d.scope;
|
||||
state
|
||||
.update(
|
||||
&p,
|
||||
scope,
|
||||
Id::new_v4(),
|
||||
Event::Edit(token, Edit::Kind(Kind::Actor)),
|
||||
)
|
||||
.unwrap();
|
||||
state
|
||||
.update(
|
||||
&p,
|
||||
scope,
|
||||
Id::new_v4(),
|
||||
Event::Edit(token, Edit::Parent(Some(parent))),
|
||||
)
|
||||
.unwrap();
|
||||
let raw = serde_json::to_value(&state).unwrap();
|
||||
let recovered: State = serde_json::from_value(raw.clone()).unwrap();
|
||||
let mut view = host(&recovered, &p, "Consumer name: Assessment lifeline");
|
||||
let mut tree = widget::Tree::new(view.as_widget());
|
||||
let (_, g) = geometry(&mut view, &mut tree, Size::new(340., 215.));
|
||||
let fields = g.fields.unwrap();
|
||||
assert!(contains(fields, g.name.unwrap()));
|
||||
assert_eq!(g.kinds.len(), 3);
|
||||
for bounds in g.kinds.values() {
|
||||
assert!(
|
||||
contains(fields, *bounds),
|
||||
"Long parent must not hide a semantic choice"
|
||||
);
|
||||
assert!(bounds.y + bounds.height <= g.parent.unwrap().y);
|
||||
}
|
||||
// Arbitrary authored detail may scroll, but its label and first line are
|
||||
// visible after the required kind controls, never in front of them.
|
||||
let parent_bounds = g.parent.unwrap();
|
||||
assert!(parent_bounds.y + 16. <= fields.y + fields.height);
|
||||
assert!(
|
||||
parent_bounds.height > fields.height,
|
||||
"Exercise genuinely overflowing parent text"
|
||||
);
|
||||
let summary = g
|
||||
.text
|
||||
.iter()
|
||||
.find(|(s, _)| s.starts_with("Semantic parent:"))
|
||||
.unwrap();
|
||||
assert!(summary.0.contains(&p.elements[&parent].name));
|
||||
assert!(summary.0.contains(&p.elements[&parent].alias));
|
||||
assert!(contains(
|
||||
Rectangle::new(Point::ORIGIN, Size::new(340., 215.)),
|
||||
g.actions.unwrap()
|
||||
));
|
||||
assert_eq!(serde_json::to_value(&recovered).unwrap(), raw);
|
||||
}
|
||||
#[test]
|
||||
fn contextual_source_layout_visible_kind_click_keeps_choice_explicit_and_name_input_works() {
|
||||
let (p, mut state) = fixture(
|
||||
Layer::Oa,
|
||||
Some(ScenarioViewpoint::ParticipantInteraction),
|
||||
false,
|
||||
);
|
||||
let original = p.clone();
|
||||
let token = state.editing.as_ref().unwrap().token;
|
||||
let scope = state.editing.as_ref().unwrap().scope;
|
||||
assert_eq!(state.editing.as_ref().unwrap().kind, None);
|
||||
let size = Size::new(340., 215.);
|
||||
let mut view = host(&state, &p, "Consumer name: Assessment lifeline");
|
||||
let mut tree = widget::Tree::new(view.as_widget());
|
||||
let (node, g) = geometry(&mut view, &mut tree, size);
|
||||
let mut messages = Vec::new();
|
||||
for kind in [Kind::Entity, Kind::Actor, Kind::Role] {
|
||||
let bounds = g.kinds[&kind.to_string()];
|
||||
assert!(contains(g.fields.unwrap(), bounds));
|
||||
for action in [
|
||||
iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left),
|
||||
iced::mouse::Event::ButtonReleased(iced::mouse::Button::Left),
|
||||
] {
|
||||
view.as_widget_mut().update(
|
||||
&mut tree,
|
||||
&iced::Event::Mouse(action),
|
||||
Layout::new(&node),
|
||||
iced::mouse::Cursor::Available(bounds.center()),
|
||||
&FontLayout,
|
||||
&mut iced::advanced::clipboard::Null,
|
||||
&mut Shell::new(&mut messages),
|
||||
&Rectangle::new(Point::ORIGIN, size),
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
matches!(messages.pop(), Some(Event::Edit(id, Edit::Kind(k))) if id == token && k == kind)
|
||||
);
|
||||
}
|
||||
let mut focus = Geometry {
|
||||
focus_name: true,
|
||||
..Geometry::default()
|
||||
};
|
||||
view.as_widget_mut()
|
||||
.operate(&mut tree, Layout::new(&node), &FontLayout, &mut focus);
|
||||
let event = iced::Event::Keyboard(iced::keyboard::Event::KeyPressed {
|
||||
key: iced::keyboard::Key::Character("Ω".into()),
|
||||
modified_key: iced::keyboard::Key::Character("Ω".into()),
|
||||
physical_key: iced::keyboard::key::Physical::Code(iced::keyboard::key::Code::KeyA),
|
||||
location: iced::keyboard::Location::Standard,
|
||||
modifiers: iced::keyboard::Modifiers::empty(),
|
||||
text: Some("Ω".into()),
|
||||
repeat: false,
|
||||
});
|
||||
view.as_widget_mut().update(
|
||||
&mut tree,
|
||||
&event,
|
||||
Layout::new(&node),
|
||||
iced::mouse::Cursor::Unavailable,
|
||||
&FontLayout,
|
||||
&mut iced::advanced::clipboard::Null,
|
||||
&mut Shell::new(&mut messages),
|
||||
&Rectangle::new(Point::ORIGIN, size),
|
||||
);
|
||||
let edit = messages.pop().unwrap();
|
||||
assert!(matches!(&edit, Event::Edit(id, Edit::Name(name)) if *id == token && name == "Ω"));
|
||||
drop(view);
|
||||
state.update(&p, scope, Id::new_v4(), edit).unwrap();
|
||||
assert_eq!(
|
||||
state.editing.as_ref().unwrap().kind,
|
||||
None,
|
||||
"Rendering/click event production must not silently adopt a kind"
|
||||
);
|
||||
assert_eq!(state.editing.as_ref().unwrap().name, "Ω");
|
||||
assert_eq!(p, original);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user