Merge upstream main; codec and kernel patches on branches merged with it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-10-16 22:12:01 +03:00
commit 0158ea533f
48 files changed
+5133 -52

No files matched your search

+31
View File
@@ -1023,6 +1023,8 @@ pub(crate) fn selection_overlay<'a>(
pub(crate) struct AuthoringDimension {
pub lines: Vec<[Point; 2]>,
pub round_grips: Vec<Point>,
pub base_grips: Vec<Point>,
pub lookup_grips: Vec<Point>,
pub dimensions: Vec<[Point; 2]>,
pub labels: Vec<(Point, String)>,
pub grips: Vec<(Point, Option<Point>)>,
@@ -2414,6 +2416,35 @@ impl canvas::Program<Message> for SelectionCanvas {
..Default::default()
});
}
for point in &dimension.base_grips {
let stroke = canvas::Stroke::default().with_color(color);
frame.stroke(&canvas::Path::circle(*point, 6.0), stroke);
frame.stroke(
&canvas::Path::line(
Point::new(point.x - 9.0, point.y),
Point::new(point.x + 9.0, point.y),
),
stroke,
);
frame.stroke(
&canvas::Path::line(
Point::new(point.x, point.y - 9.0),
Point::new(point.x, point.y + 9.0),
),
stroke,
);
}
for point in &dimension.lookup_grips {
frame.fill(
&canvas::Path::new(|p| {
p.move_to(Point::new(point.x - 5.0, point.y - 4.0));
p.line_to(Point::new(point.x + 5.0, point.y - 4.0));
p.line_to(Point::new(point.x, point.y + 5.0));
p.close();
}),
color,
);
}
for point in &dimension.round_grips {
frame.fill(&canvas::Path::circle(*point, 3.5), color);
}
+34 -8
View File
@@ -193,6 +193,9 @@ fn catalog(
visibility_error: Option<&'static str>,
point_error: Option<&'static str>,
flip_error: Option<&'static str>,
lookup_error: Option<&'static str>,
move_error: Option<&'static str>,
scale_error: Option<&'static str>,
) -> Element<'static, Message> {
let (entries, unavailable) = match tab {
Tab::Parameters => (
@@ -212,7 +215,11 @@ fn catalog(
let distance = tab == Tab::Parameters && matches!(*label, "Linear" | "Polar" | "XY");
let orientation = tab == Tab::Parameters && matches!(*label,"Rotation"|"Alignment"|"Flip");
let orientation_error = if *label=="Flip" {flip_error} else {point_error};
let enabled = (orientation && orientation_error.is_none()) || (visibility && visibility_error.is_none()) || ((point || distance) && point_error.is_none());
let single=tab==Tab::Parameters&&matches!(*label,"Lookup"|"Basepoint");
let lookup_action=tab==Tab::Actions&&*label=="Lookup";
let geometry_action=tab==Tab::Actions&&matches!(*label,"Move"|"Scale");
let action_error=if *label=="Scale" {scale_error}else{move_error};
let enabled = (geometry_action&&action_error.is_none())||(single&&point_error.is_none())||(lookup_action&&lookup_error.is_none())||(orientation && orientation_error.is_none()) || (visibility && visibility_error.is_none()) || ((point || distance) && point_error.is_none());
let base = if enabled {
icons::semantic(icon, 28.0)
} else {
@@ -221,10 +228,11 @@ fn catalog(
let icon = if tab != Tab::Parameters && *label != "Block Properties Table" {
stack![
base,
container(icons::semantic_disabled(
include_bytes!("../../../assets/icons/ui/bolt.svg"),
14.0
))
container(if enabled {
icons::semantic(include_bytes!("../../../assets/icons/ui/bolt.svg"), 14.0)
} else {
icons::semantic_disabled(include_bytes!("../../../assets/icons/ui/bolt.svg"), 14.0)
})
.width(28)
.height(28)
.align_right(Fill)
@@ -235,8 +243,8 @@ fn catalog(
base
};
let message = enabled.then(|| Message::RibbonToolClick {
tool_id: format!("BPARAMETER_{}", label.to_ascii_uppercase()),
event: crate::modules::ModuleEvent::Command(format!("BPARAMETER {}", label.to_ascii_uppercase())),
tool_id: if geometry_action {format!("BACTION_{}",label.to_ascii_uppercase())}else if lookup_action {"BLOOKUPTABLE".into()}else{format!("BPARAMETER_{}", label.to_ascii_uppercase())},
event: crate::modules::ModuleEvent::Command(if geometry_action {format!("BACTION {}",label.to_ascii_uppercase())}else if lookup_action {"BLOOKUPTABLE".into()}else{format!("BPARAMETER {}", label.to_ascii_uppercase())}),
});
let description = if visibility {
crate::t!(visibility_error
@@ -253,6 +261,12 @@ fn catalog(
"Alignment" => "Place the alignment base point and tangent direction.",
_ => "Place a reflection line and its flip-state label. Associate a Flip action to mirror geometry.",
})).into_owned()
} else if single {
crate::t!(point_error.unwrap_or(if *label=="Basepoint" {"Place or relocate the dynamic block insertion anchor."}else{"Place a Lookup parameter, then use the Lookup action to edit its property table."})).into_owned()
} else if geometry_action {
crate::t!(action_error.unwrap_or("Select a parameter endpoint, select the objects it controls, then place the action marker. Test it using the parameter grip in Test Block.")).into_owned()
} else if lookup_action {
crate::t!(lookup_error.unwrap_or("Create or edit property Lookup tables for this block.")).into_owned()
} else {
unavailable.to_string()
};
@@ -265,6 +279,12 @@ fn catalog(
container(entry).id(match *label { "Linear" => "authoring-tool-linear", "Polar" => "authoring-tool-polar", _ => "authoring-tool-xy" }).into()
} else if orientation {
container(entry).id(match *label {"Rotation"=>"authoring-tool-rotation","Alignment"=>"authoring-tool-alignment",_=>"authoring-tool-flip"}).into()
} else if single {
container(entry).id(if *label=="Lookup" {"authoring-tool-lookup"}else{"authoring-tool-basepoint"}).into()
} else if geometry_action {
container(entry).id(if *label=="Move" {"authoring-tool-move-action"}else{"authoring-tool-scale-action"}).into()
} else if lookup_action {
container(entry).id("authoring-tool-lookup-table").into()
} else {
entry
}
@@ -378,6 +398,9 @@ pub fn view(
visibility_error: Option<&'static str>,
point_error: Option<&'static str>,
flip_error: Option<&'static str>,
lookup_error: Option<&'static str>,
move_error: Option<&'static str>,
scale_error: Option<&'static str>,
) -> Element<'static, Message> {
let tabs = column(Tab::ALL.into_iter().map(|category| {
let label = category.label();
@@ -431,7 +454,10 @@ pub fn view(
selected_tab,
visibility_error,
point_error,
flip_error
flip_error,
lookup_error,
move_error,
scale_error
))
.width(Fill)
.height(Fill),
+319
View File
@@ -0,0 +1,319 @@
use crate::app::Message;
use crate::scene::block_lookup::{self, Property};
use codec::{
objects::{BlockLookupAction, BlockLookupParameter},
Handle,
};
use iced::{
widget::{
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
},
Element, Fill,
};
#[derive(Debug, Clone)]
pub enum Action {
Select(usize),
Property(usize),
AddProperty,
RemoveProperty(usize),
Cell(usize, usize, String),
AddRow,
DeleteRow(usize),
MoveRow(usize, bool),
Reverse(usize, bool),
Audit,
Apply,
}
pub(crate) struct Draft {
pub parameter: Handle,
pub original_parameter: BlockLookupParameter,
pub original: Option<(Handle, BlockLookupAction)>,
pub edited: BlockLookupAction,
}
impl Draft {
pub(crate) fn changed(&self) -> bool {
match &self.original {
Some((_, original)) => original != &self.edited,
None => self.edited != block_lookup::empty_table(&self.original_parameter),
}
}
}
pub(crate) struct State {
pub document: u64,
pub definition: Handle,
pub epoch: u64,
pub drafts: Vec<Draft>,
pub selected: usize,
pub properties: Vec<Property>,
pub selected_property: Option<usize>,
pub error: Option<String>,
pub notice: Option<String>,
}
impl State {
pub(crate) fn edit(&mut self, action: Action) {
self.error = None;
self.notice = None;
match action {
Action::Select(index) if index < self.drafts.len() => {
self.selected = index;
self.selected_property = None;
return;
}
Action::Property(index) if index < self.properties.len() => {
self.selected_property = Some(index);
return;
}
_ => {}
}
let Some(draft) = self.drafts.get_mut(self.selected) else {
return;
};
let table = &mut draft.edited;
let cols = table.columns.len();
let rows = table.row_count.max(0) as usize;
match action {
Action::AddProperty => {
let Some(property) = self.selected_property.and_then(|i| self.properties.get(i))
else {
return;
};
if cols >= 64
|| table.columns.iter().any(|c| {
c.node_id == property.column.node_id
&& c.connection_name == property.column.connection_name
})
{
return;
}
let index = table
.columns
.iter()
.position(|c| c.lookup_property)
.unwrap_or(cols);
for row in (0..rows).rev() {
table
.expressions
.insert(row * cols + index, property.value.clone());
}
table.columns.insert(index, property.column.clone());
table.column_count += 1;
self.selected_property = None;
}
Action::RemoveProperty(index)
if table.columns.get(index).is_some_and(|c| !c.lookup_property) =>
{
for row in (0..rows).rev() {
table.expressions.remove(row * cols + index);
}
table.columns.remove(index);
table.column_count -= 1;
}
Action::Cell(row, col, value) if row < rows && col < cols => {
table.expressions[row * cols + col] = value;
}
Action::AddRow if rows < 1000 => {
for column in &table.columns {
let value = if column.lookup_property {
format!("Value{}", rows + 1)
} else {
self.properties
.iter()
.find(|p| {
p.column.node_id == column.node_id
&& p.column.connection_name == column.connection_name
})
.map(|p| p.value.clone())
.unwrap_or_default()
};
table.expressions.push(value);
}
table.row_count += 1;
}
Action::DeleteRow(row) if row < rows => {
table.expressions.drain(row * cols..(row + 1) * cols);
table.row_count -= 1;
}
Action::MoveRow(row, up) if row < rows => {
let next = if up {
row.checked_sub(1)
} else {
(row + 1 < rows).then_some(row + 1)
};
if let Some(next) = next {
for col in 0..cols {
table.expressions.swap(row * cols + col, next * cols + col);
}
}
}
Action::Reverse(index, value)
if table.columns.get(index).is_some_and(|c| c.lookup_property) =>
{
table.columns[index].writable = value;
}
Action::Audit => {
match block_lookup::validate(table, draft.original.as_ref().map(|(_, t)| t)) {
Ok(()) => self.notice = Some(crate::t!("Table audit passed.").into_owned()),
Err(error) => self.error = Some(crate::t!(error.as_str()).into_owned()),
}
}
_ => {}
}
}
}
pub(crate) fn view(state: &State, sizing: crate::ui::modal::ModalSizing) -> Element<'_, Message> {
let send = |a| Message::BlockLookup(a);
let draft = &state.drafts[state.selected];
let table = &draft.edited;
let cols = table.columns.len();
let rows = table.row_count.max(0) as usize;
let mut headings = row![text(" ").width(28)].spacing(6);
for (index, column) in table.columns.iter().enumerate() {
let name = if column.lookup_property {
state
.drafts
.iter()
.find(|d| {
d.original_parameter
.parameter
.parameter
.element
.eval
.node_id
== column.node_id
})
.map(|d| d.original_parameter.lookup_name.clone())
.unwrap_or_else(|| crate::t!("Lookup value").into_owned())
} else {
state
.properties
.iter()
.find(|p| {
p.column.node_id == column.node_id
&& p.column.connection_name == column.connection_name
})
.map(|p| p.label.clone())
.unwrap_or_else(|| crate::t!("Input property").into_owned())
};
let mut header = column![text(name).size(13)];
if column.lookup_property {
header = header.push(
row![
checkbox(column.writable).on_toggle(move |v| send(Action::Reverse(index, v))),
text(crate::t!("Reverse lookup").into_owned()).size(11)
]
.spacing(4),
);
} else {
header = header.push(
button(text(crate::t!("Remove").into_owned()).size(11))
.on_press(send(Action::RemoveProperty(index))),
);
}
headings = headings.push(container(header.spacing(4)).width(160));
}
let mut grid = column![headings].spacing(7);
for row_index in 0..rows {
let mut cells = row![text((row_index + 1).to_string()).width(28)].spacing(6);
for col_index in 0..cols {
cells = cells.push(
text_input("", &table.expressions[row_index * cols + col_index])
.on_input(move |v| send(Action::Cell(row_index, col_index, v)))
.width(160)
.style(crate::ui::style::form::field_style),
);
}
cells =
cells
.push(button(text("↑")).on_press_maybe(
(row_index > 0).then_some(send(Action::MoveRow(row_index, true))),
))
.push(button(text("↓")).on_press_maybe(
(row_index + 1 < rows).then_some(send(Action::MoveRow(row_index, false))),
))
.push(
button(text(crate::t!("Delete").into_owned()))
.on_press(send(Action::DeleteRow(row_index))),
);
grid = grid.push(cells);
}
let available = state
.properties
.iter()
.enumerate()
.filter(|(_, p)| {
!table.columns.iter().any(|c| {
c.node_id == p.column.node_id && c.connection_name == p.column.connection_name
})
})
.map(|(i, _)| i)
.collect::<Vec<_>>();
container(
column![
row![
text(crate::t!("Lookup parameter").into_owned()),
pick_list(
Some(state.selected),
(0..state.drafts.len()).collect::<Vec<_>>(),
|i: &usize| state.drafts[*i].original_parameter.lookup_name.clone()
)
.on_select(move |i| send(Action::Select(i)))
.width(Fill)
]
.spacing(10),
row![
pick_list(state.selected_property, available, |i: &usize| state
.properties[*i]
.label
.clone())
.on_select(move |i| send(Action::Property(i)))
.placeholder(crate::t!("Choose input property").into_owned())
.width(Fill),
button(text(crate::t!("Add Property").into_owned()))
.on_press_maybe(state.selected_property.map(|_| send(Action::AddProperty)))
]
.spacing(8),
text(
crate::t!(
"Add Linear or Polar distance inputs. Existing table columns are retained."
)
.into_owned()
)
.size(12),
scrollable(grid)
.direction(scrollable::Direction::Both {
vertical: Default::default(),
horizontal: Default::default()
})
.height(Fill)
.width(Fill),
row![
button(text(crate::t!("Add Row").into_owned()))
.on_press_maybe((rows < 1000).then_some(send(Action::AddRow))),
button(text(crate::t!("Audit").into_owned())).on_press(send(Action::Audit)),
Space::new().width(Fill)
]
.spacing(8),
text(state.error.clone().unwrap_or_default())
.size(12)
.style(text::danger),
text(state.notice.clone().unwrap_or_default()).size(12),
row![
Space::new().width(Fill),
button(text(crate::t!("OK").into_owned()))
.on_press(send(Action::Apply))
.style(button::primary),
button(text(crate::t!("Cancel").into_owned())).on_press(Message::CloseModal)
]
.spacing(8),
]
.spacing(12),
)
.padding(16)
.width(sizing.width)
.height(sizing.height)
.into()
}
+1
View File
@@ -11,6 +11,7 @@ pub mod block_edit_picker;
pub mod block_palette;
pub mod block_save_as;
pub mod block_visibility;
pub mod block_lookup;
pub mod browser;
pub mod count_palette;
pub mod drafting_settings;