Skip to content

Commit a542fec

Browse files
committed
Bind grid navigation paths through Rml data
1 parent 68dcbbc commit a542fec

9 files changed

Lines changed: 78 additions & 9 deletions

File tree

‎native/src/sbc/panels/controls/asset_picker.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ impl AssetPicker {
158158
r#"<div class="dialog-content">"#,
159159
r#"<div class="asset-path-nav">"#,
160160
r#"<button id="asset-up" class="dialog-button">Up</button>"#,
161-
r#"<span class="asset-path">{{ path }}</span></div>"#,
161+
r#"<span class="asset-path">{{{{ path }}}}</span></div>"#,
162162
r#"{grid}"#,
163163
r#"</div>"#,
164164
r#"<div class="dialog-footer">"#,
@@ -243,6 +243,7 @@ impl Modal for AssetPicker {
243243
.rml_ui()
244244
.create_data_model(context, "asset_picker")?;
245245
self.path = Some(data_model.bind("path", String::new())?);
246+
self.grid.prepare_data_model(&data_model)?;
246247
Ok(())
247248
}
248249

‎native/src/sbc/panels/controls/grid.rs‎

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::rc::Rc;
1313

1414
use spring_native::{
1515
prelude::{Error, NativeInterfaceRef},
16-
RmlDataTextRows,
16+
RmlDataModel, RmlDataTextRows, RmlDataVariable,
1717
};
1818

1919
use crate::sbc::vfs::{join_entry, leaf, normalize_extensions, vfs_files};
@@ -59,6 +59,10 @@ pub(crate) struct GridView {
5959
clicks: ClickQueue,
6060
item_size: u32,
6161
navigation: Option<GridNavigation>,
62+
/// The navigation breadcrumb belongs to the surrounding screen's model.
63+
/// Unlike the grid rows, it exists in markup parsed before this grid has a
64+
/// chance to create its own item model.
65+
navigation_path: Option<RmlDataVariable<'static, String>>,
6266
/// Engine-owned text collection backing the static `data-for` scaffold.
6367
/// It becomes invalid with its document, so `render` recreates it after a
6468
/// panel reload.
@@ -80,6 +84,7 @@ impl GridView {
8084
clicks: Rc::new(RefCell::new(Vec::new())),
8185
item_size,
8286
navigation: None,
87+
navigation_path: None,
8388
rows: None,
8489
model_context: None,
8590
bound_document: None,
@@ -199,11 +204,16 @@ impl GridView {
199204

200205
pub(crate) fn container_rml(&self) -> String {
201206
if self.navigation.is_some() {
207+
let path = self
208+
.navigation_path
209+
.as_ref()
210+
.map(|_| format!("{{{{ {} }}}}", self.navigation_path_name()))
211+
.unwrap_or_default();
202212
return format!(
203213
r#"<div id="{id}-picker" class="grid-picker">
204214
<div class="grid-navigation">
205215
<button id="{id}-up" class="dialog-button">Up</button>
206-
<span id="{id}-path" class="asset-path"></span>
216+
<span id="{id}-path" class="asset-path">{path}</span>
207217
</div>
208218
<div id="{id}" class="grid-container"></div>
209219
</div>"#,
@@ -221,8 +231,24 @@ impl GridView {
221231
self.items_dirty = true;
222232
}
223233

234+
/// Bind the breadcrumb before its containing markup is parsed. The item
235+
/// rows intentionally keep their own short-lived model because their
236+
/// scaffold is inserted only after the grid knows its document context.
237+
pub(crate) fn prepare_data_model(
238+
&mut self,
239+
model: &RmlDataModel<'static>,
240+
) -> Result<(), Error> {
241+
let Some(navigation) = &self.navigation else {
242+
return Ok(());
243+
};
244+
self.navigation_path =
245+
Some(model.bind(&self.navigation_path_name(), navigation.dir.clone())?);
246+
Ok(())
247+
}
248+
224249
/// Drop document-owned bindings after its panel has been rebuilt.
225250
pub(crate) fn forget_bindings(&mut self) {
251+
self.navigation_path = None;
226252
self.rows = None;
227253
self.model_context = None;
228254
self.bound_document = None;
@@ -242,6 +268,10 @@ impl GridView {
242268
self.forget_bindings();
243269
Ok(())
244270
}
271+
272+
fn navigation_path_name(&self) -> String {
273+
format!("{}_path", self.model_name())
274+
}
245275
}
246276

247277
/// VFS listing for the asset picker: directories first, then files, both sorted.

‎native/src/sbc/panels/controls/grid/render.rs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,14 @@ impl GridView {
100100
return Ok(());
101101
};
102102
let rml = interface.rml_ui();
103-
if let Some(path) =
103+
if let Some(path) = &self.navigation_path {
104+
path.set(navigation.dir.clone())?;
105+
} else if let Some(path) =
104106
element_by_id(interface, document, &format!("{}-path", self.container_id))
105107
{
108+
// Grids may also be mounted by legacy callers that do not own a
109+
// pre-parsed data model yet. Keep that path working while those
110+
// callers are migrated.
106111
rml.element_set_inner_rml(path, &escape_rml(&navigation.dir))?;
107112
}
108113
if let Some(up) = element_by_id(interface, document, &format!("{}-up", self.container_id)) {

‎native/src/sbc/panels/dialogs/file_dialog.rs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,11 @@ impl FileDialog {
193193
concat!(
194194
r#"<div id="file-dialog" class="picker-backdrop hidden" data-model="file_dialog">"#,
195195
r#"<div class="dialog picker-dialog asset-dialog">"#,
196-
r#"<div class="dialog-header"><span class="dialog-title">{{ title }}</span></div>"#,
196+
r#"<div class="dialog-header"><span class="dialog-title">{{{{ title }}}}</span></div>"#,
197197
r#"<div class="dialog-content">"#,
198198
r#"<div class="asset-path-nav">"#,
199199
r#"<button id="fd-up" class="dialog-button">Up</button>"#,
200-
r#"<span class="asset-path">{{ path }}</span></div>"#,
200+
r#"<span class="asset-path">{{{{ path }}}}</span></div>"#,
201201
r#"{grid}"#,
202202
r#"{form}"#,
203203
r#"</div>"#,
@@ -347,6 +347,8 @@ impl Modal for FileDialog {
347347
self.title = Some(data_model.bind("title", String::new())?);
348348
self.path = Some(data_model.bind("path", String::new())?);
349349
self.file_type_options = Some(data_model.bind_option_rows("types")?);
350+
self.form.prepare_data_model(&data_model)?;
351+
self.grid.prepare_data_model(&data_model)?;
350352
Ok(())
351353
}
352354

‎native/src/sbc/panels/dialogs/form.rs‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
//! DOM reads/writes, and value storage remain owned by `Runtime` and the panel's
55
//! shared input/session pipeline.
66
7-
use spring_native::prelude::{Error, NativeInterfaceRef};
7+
use spring_native::{
8+
prelude::{Error, NativeInterfaceRef},
9+
RmlDataModel,
10+
};
811

912
use crate::sbc::command_system::model::Models;
1013
use crate::sbc::panels::editor::Editor;
@@ -74,6 +77,16 @@ impl<Id: Copy + Eq + 'static> DialogForm<Id> {
7477
self.runtime.generate_rml()
7578
}
7679

80+
/// Prepare the form's fields in the modal's model before its RML is
81+
/// parsed. This keeps dialog controls on the same typed update path as
82+
/// editor controls.
83+
pub(crate) fn prepare_data_model(
84+
&mut self,
85+
model: &RmlDataModel<'static>,
86+
) -> Result<(), Error> {
87+
self.runtime.prepare_data_model(model)
88+
}
89+
7790
pub(crate) fn bind(
7891
&mut self,
7992
interface: &NativeInterfaceRef,

‎native/src/sbc/panels/fields/asset.rs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ impl AssetField {
5858
"field_{}_display",
5959
self.name
6060
.chars()
61-
.map(|character| if character.is_ascii_alphanumeric() { character } else { '_' })
61+
.map(|character| if character.is_ascii_alphanumeric() {
62+
character
63+
} else {
64+
'_'
65+
})
6266
.collect::<String>(),
6367
)
6468
}

‎native/src/sbc/panels/runtime/adapter.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ impl<B: Behavior> Editor for Runtime<B> {
197197
for entry in self.model.fields_mut() {
198198
entry.field.prepare_data_model(model)?;
199199
}
200+
for grid in self.model.grids_mut() {
201+
grid.prepare_data_model(model)?;
202+
}
200203
Ok(())
201204
}
202205

‎native/src/sbc/panels/runtime/typed_fields.rs‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
//! the live value. Editor code reads and writes through typed accessors; the
33
//! DOM plumbing reuses the dyn `Field` implementations underneath.
44
5-
use spring_native::prelude::{Error, NativeInterfaceRef};
5+
use spring_native::{
6+
prelude::{Error, NativeInterfaceRef},
7+
RmlDataModel,
8+
};
69

710
use crate::sbc::panels::controls::grid::GridView;
811
use crate::sbc::panels::field::{Field, FieldValue};
@@ -302,6 +305,13 @@ impl AssetGrid {
302305
self.grid.container_rml()
303306
}
304307

308+
pub(crate) fn prepare_data_model(
309+
&mut self,
310+
model: &RmlDataModel<'static>,
311+
) -> Result<(), Error> {
312+
self.grid.prepare_data_model(model)
313+
}
314+
305315
pub(crate) fn bind(
306316
&mut self,
307317
interface: &NativeInterfaceRef,

‎native/src/sbc/project/new_project_dialog.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ impl Modal for NewProjectDialog {
242242
.rml_ui()
243243
.create_data_model(context, "new_project")?;
244244
self.map_options = Some(data_model.bind_option_rows("maps")?);
245+
self.form.prepare_data_model(&data_model)?;
245246
Ok(())
246247
}
247248

0 commit comments

Comments
 (0)