mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 10:09:32 -06:00
3D: Layer > New > 3D Primitive... dialog for a primitive's type, name and dimensions (#1734)
OK runs layer.newPrimitive, which switches a Classic 3D comp to Advanced 3D in the same undo step. The dimension fields are the Geometry Options the engine builds for the chosen type.
This commit is contained in:
@@ -733,6 +733,7 @@ Layer
|
||||
Shape Layer | layer.newShape
|
||||
Adjustment Layer | layer.newAdjustment
|
||||
Content-Aware Fill Layer... | layer.newContentAwareFill
|
||||
3D Primitive... | layer.newPrimitive
|
||||
Cube | layer.new3dPrimitive {"kind":"cube"}
|
||||
Sphere | layer.new3dPrimitive {"kind":"sphere"}
|
||||
Plane | layer.new3dPrimitive {"kind":"plane"}
|
||||
|
||||
@@ -505,6 +505,7 @@ const SIMPLIFIED_CHINESE: &[(&str, &str, &str)] = &[
|
||||
("layer.newShape", "Shape Layer", "形状图层"),
|
||||
("layer.newAdjustment", "Adjustment Layer", "调整图层"),
|
||||
("layer.newContentAwareFill", "Content-Aware Fill Layer...", "内容识别填充图层..."),
|
||||
("layer.newPrimitive", "3D Primitive...", "3D 基本体..."),
|
||||
("layer.new3dPrimitive", "Cube", "立方体"),
|
||||
("layer.new3dPrimitive", "Sphere", "球体"),
|
||||
("layer.new3dPrimitive", "Plane", "平面"),
|
||||
@@ -1139,6 +1140,7 @@ const TRADITIONAL_CHINESE: &[(&str, &str, &str)] = &[
|
||||
("layer.newShape", "Shape Layer", "形狀圖層"),
|
||||
("layer.newAdjustment", "Adjustment Layer", "調整圖層"),
|
||||
("layer.newContentAwareFill", "Content-Aware Fill Layer...", "內容感知填滿圖層..."),
|
||||
("layer.newPrimitive", "3D Primitive...", "3D 基本體..."),
|
||||
("layer.new3dPrimitive", "Cube", "立方體"),
|
||||
("layer.new3dPrimitive", "Sphere", "球體"),
|
||||
("layer.new3dPrimitive", "Plane", "平面"),
|
||||
@@ -1770,6 +1772,7 @@ const JAPANESE: &[(&str, &str, &str)] = &[
|
||||
("layer.newShape", "Shape Layer", "シェイプレイヤー"),
|
||||
("layer.newAdjustment", "Adjustment Layer", "調整レイヤー"),
|
||||
("layer.newContentAwareFill", "Content-Aware Fill Layer...", "コンテンツに応じた塗りつぶしレイヤー..."),
|
||||
("layer.newPrimitive", "3D Primitive...", "3D プリミティブ..."),
|
||||
("layer.new3dPrimitive", "Cube", "立方体"),
|
||||
("layer.new3dPrimitive", "Sphere", "球体"),
|
||||
("layer.new3dPrimitive", "Plane", "平面"),
|
||||
@@ -2404,6 +2407,7 @@ const UKRAINIAN: &[(&str, &str, &str)] = &[
|
||||
("layer.newShape", "Shape Layer", "Шар фігури"),
|
||||
("layer.newAdjustment", "Adjustment Layer", "Коригувальний шар"),
|
||||
("layer.newContentAwareFill", "Content-Aware Fill Layer...", "Шар заливки з урахуванням вмісту..."),
|
||||
("layer.newPrimitive", "3D Primitive...", "3D-примітив..."),
|
||||
("layer.new3dPrimitive", "Cube", "Куб"),
|
||||
("layer.new3dPrimitive", "Sphere", "Сфера"),
|
||||
("layer.new3dPrimitive", "Plane", "Площина"),
|
||||
@@ -3039,6 +3043,7 @@ const SPANISH: &[(&str, &str, &str)] = &[
|
||||
("layer.newShape", "Shape Layer", "Capa de forma"),
|
||||
("layer.newAdjustment", "Adjustment Layer", "Capa de ajuste"),
|
||||
("layer.newContentAwareFill", "Content-Aware Fill Layer...", "Capa de relleno según el contenido..."),
|
||||
("layer.newPrimitive", "3D Primitive...", "Primitiva 3D..."),
|
||||
("layer.new3dPrimitive", "Cube", "Cubo"),
|
||||
("layer.new3dPrimitive", "Sphere", "Esfera"),
|
||||
("layer.new3dPrimitive", "Plane", "Plano"),
|
||||
@@ -3672,6 +3677,7 @@ const PORTUGUESE: &[(&str, &str, &str)] = &[
|
||||
("layer.newShape", "Shape Layer", "Camada de forma"),
|
||||
("layer.newAdjustment", "Adjustment Layer", "Camada de ajuste"),
|
||||
("layer.newContentAwareFill", "Content-Aware Fill Layer...", "Camada de preenchimento sensível ao conteúdo..."),
|
||||
("layer.newPrimitive", "3D Primitive...", "Primitiva 3D..."),
|
||||
("layer.new3dPrimitive", "Cube", "Cubo"),
|
||||
("layer.new3dPrimitive", "Sphere", "Esfera"),
|
||||
("layer.new3dPrimitive", "Plane", "Plano"),
|
||||
@@ -4312,6 +4318,7 @@ const RUSSIAN: &[(&str, &str, &str)] = &[
|
||||
("layer.newShape", "Shape Layer", "Слой-фигура"),
|
||||
("layer.newAdjustment", "Adjustment Layer", "Корректирующий слой"),
|
||||
("layer.newContentAwareFill", "Content-Aware Fill Layer...", "Слой заливки с учетом содержимого..."),
|
||||
("layer.newPrimitive", "3D Primitive...", "3D-примитив..."),
|
||||
("layer.new3dPrimitive", "Cube", "Куб"),
|
||||
("layer.new3dPrimitive", "Sphere", "Сфера"),
|
||||
("layer.new3dPrimitive", "Plane", "Плоскость"),
|
||||
@@ -4945,6 +4952,7 @@ const FRENCH: &[(&str, &str, &str)] = &[
|
||||
("layer.newShape", "Shape Layer", "Calque de forme"),
|
||||
("layer.newAdjustment", "Adjustment Layer", "Calque de réglage"),
|
||||
("layer.newContentAwareFill", "Content-Aware Fill Layer...", "Calque de remplissage d’après le contenu..."),
|
||||
("layer.newPrimitive", "3D Primitive...", "Primitive 3D..."),
|
||||
("layer.new3dPrimitive", "Cube", "Cube"),
|
||||
("layer.new3dPrimitive", "Sphere", "Sphère"),
|
||||
("layer.new3dPrimitive", "Plane", "Plan"),
|
||||
@@ -5578,6 +5586,7 @@ const GERMAN: &[(&str, &str, &str)] = &[
|
||||
("layer.newShape", "Shape Layer", "Formebene"),
|
||||
("layer.newAdjustment", "Adjustment Layer", "Einstellungsebene"),
|
||||
("layer.newContentAwareFill", "Content-Aware Fill Layer...", "Ebene für inhaltsbasierte Füllung..."),
|
||||
("layer.newPrimitive", "3D Primitive...", "3D-Grundkörper..."),
|
||||
("layer.new3dPrimitive", "Cube", "Würfel"),
|
||||
("layer.new3dPrimitive", "Sphere", "Kugel"),
|
||||
("layer.new3dPrimitive", "Plane", "Fläche"),
|
||||
@@ -6212,6 +6221,7 @@ const CZECH: &[(&str, &str, &str)] = &[
|
||||
("layer.newShape", "Shape Layer", "Vrstva tvaru"),
|
||||
("layer.newAdjustment", "Adjustment Layer", "Vrstva úprav"),
|
||||
("layer.newContentAwareFill", "Content-Aware Fill Layer...", "Vrstva výplně podle obsahu..."),
|
||||
("layer.newPrimitive", "3D Primitive...", "3D primitivum..."),
|
||||
("layer.new3dPrimitive", "Cube", "Krychle"),
|
||||
("layer.new3dPrimitive", "Sphere", "Koule"),
|
||||
("layer.new3dPrimitive", "Plane", "Rovina"),
|
||||
|
||||
@@ -72,6 +72,8 @@ pub enum Dialog {
|
||||
ViewOptions,
|
||||
CameraSettings,
|
||||
LightSettings,
|
||||
/// Layer ▸ New ▸ 3D Primitive… (`panels::primitive_dialog`).
|
||||
NewPrimitive,
|
||||
KeyVelocity,
|
||||
KeyInterpolation,
|
||||
TimeStretch,
|
||||
|
||||
@@ -399,6 +399,10 @@ pub fn invoke(app: &mut EffectcraftApp, ctx: &egui::Context, id: &str, params: V
|
||||
if crate::panels::dialogs_3d::route(app, id, ¶ms)? {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
// New 3D Primitive without parameters opens its dialog.
|
||||
if crate::panels::primitive_dialog::route(app, id, ¶ms)? {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
// Layer Settings on a solid, adjustment layer or null opens its settings dialog.
|
||||
if crate::panels::dialogs::route_layer_settings(app, id, ¶ms)? {
|
||||
return Ok(Value::Null);
|
||||
|
||||
@@ -47,6 +47,7 @@ pub struct DialogState {
|
||||
pub info: (String, String),
|
||||
pub camera: super::dialogs_3d::CameraDraft,
|
||||
pub light: super::dialogs_3d::LightDraft,
|
||||
pub primitive: super::primitive_dialog::PrimitiveDraft,
|
||||
pub velocity: super::key_dialogs::VelocityDraft,
|
||||
pub interp: super::key_dialogs::InterpDraft,
|
||||
pub stretch: super::key_dialogs::StretchDraft,
|
||||
@@ -248,6 +249,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context) {
|
||||
Dialog::ViewOptions => super::forms::show_view_options(app, ctx, &t),
|
||||
Dialog::CameraSettings => super::dialogs_3d::camera(app, ctx, &t),
|
||||
Dialog::LightSettings => super::dialogs_3d::light(app, ctx, &t),
|
||||
Dialog::NewPrimitive => super::primitive_dialog::dialog(app, ctx, &t),
|
||||
Dialog::KeyVelocity => super::key_dialogs::velocity(app, ctx, &t),
|
||||
Dialog::KeyInterpolation => super::key_dialogs::interpolation(app, ctx, &t),
|
||||
Dialog::TimeStretch => super::key_dialogs::time_stretch(app, ctx, &t),
|
||||
|
||||
@@ -35,6 +35,7 @@ pub mod paint_panels;
|
||||
pub mod panel_kit;
|
||||
pub mod path_vr_panels;
|
||||
pub mod precomp;
|
||||
pub mod primitive_dialog;
|
||||
pub mod project;
|
||||
pub mod properties;
|
||||
pub mod puppet_tool;
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
//! New 3D Primitive dialog (Layer ▸ New ▸ 3D Primitive…): the primitive's type, name and
|
||||
//! dimensions. OK runs `layer.newPrimitive`, which switches a Classic 3D comp to Advanced 3D;
|
||||
//! the layer's Geometry Options edit it afterwards.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::i18n::tr;
|
||||
use effectcraft_engine::project::build::{Ids, primitive_geometry_options};
|
||||
use effectcraft_engine::project::{ParamUi, PrimitiveKind, Property};
|
||||
use egui::{Color32, vec2};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::theme::Tokens;
|
||||
use crate::{Dialog, EffectcraftApp};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PrimitiveDraft {
|
||||
pub kind: PrimitiveKind,
|
||||
pub name: String,
|
||||
/// Whether the name was typed; until then it follows the type.
|
||||
pub named: bool,
|
||||
/// Dimensions by Geometry Options match id, kept when the type changes (a sphere's radius
|
||||
/// carries over to a cone).
|
||||
pub dims: BTreeMap<String, f64>,
|
||||
}
|
||||
|
||||
/// The dimensions a primitive type has: its Geometry Options as the engine builds them.
|
||||
fn dimensions(kind: PrimitiveKind) -> Vec<Property> {
|
||||
let mut next = 0;
|
||||
primitive_geometry_options(&mut Ids(&mut next), kind).props().cloned().collect()
|
||||
}
|
||||
|
||||
/// Open the dialog for a new primitive in the active comp.
|
||||
pub fn open(app: &mut EffectcraftApp) -> Result<(), String> {
|
||||
app.session.active_comp().ok_or("no composition is open")?;
|
||||
let kind = PrimitiveKind::Cube;
|
||||
app.dialog_state.primitive = PrimitiveDraft { kind, name: kind.label().to_string(), named: false, dims: BTreeMap::new() };
|
||||
app.dialog = Some(Dialog::NewPrimitive);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
|
||||
let mut d = app.dialog_state.primitive.clone();
|
||||
let (mut ok, mut close) = (false, false);
|
||||
super::dialogs::modal(ctx, "New 3D Primitive", vec2(420.0, 300.0), t, |ui| {
|
||||
egui::Grid::new("primitive-grid").num_columns(2).spacing([14.0, 9.0]).show(ui, |ui| {
|
||||
ui.label(tr("Type"));
|
||||
let combo = egui::ComboBox::from_id_salt("primitive-type").selected_text(tr(d.kind.label())).show_ui(ui, |ui| {
|
||||
for (i, k) in PrimitiveKind::ALL.into_iter().enumerate() {
|
||||
let item = ui.selectable_value(&mut d.kind, k, tr(k.label()));
|
||||
app.auto.add(&format!("dialog.primitive.typeItem.{i}"), item.rect, k.label());
|
||||
}
|
||||
});
|
||||
app.auto.add_response("dialog.primitive.type", &combo.response, "Type");
|
||||
if !d.named {
|
||||
d.name = d.kind.label().to_string();
|
||||
}
|
||||
ui.end_row();
|
||||
ui.label(tr("Name"));
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut d.name).desired_width(220.0));
|
||||
d.named |= r.changed();
|
||||
app.auto.add_response("dialog.primitive.name", &r, "Name");
|
||||
ui.end_row();
|
||||
for p in dimensions(d.kind) {
|
||||
let ParamUi::Slider { min, max, decimals, .. } = p.ui else { continue };
|
||||
let v = d.dims.entry(p.match_id.clone()).or_insert(p.value.as_f64());
|
||||
ui.label(tr(&p.name));
|
||||
// Counts (segments, rings) are whole numbers; sizes are pixels.
|
||||
let suffix = if decimals == 0 { "" } else { " px" };
|
||||
let r = ui.add(crate::widgets::drag_value(egui::DragValue::new(v).range(min..=max).max_decimals(decimals as usize).suffix(suffix)));
|
||||
app.auto.add_response(&format!("dialog.primitive.{}", p.match_id), &r, &p.name);
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
ui.add_space(14.0);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let b = ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent));
|
||||
app.auto.add("dialog.primitive.ok", b.rect, "OK");
|
||||
ok |= b.clicked();
|
||||
let c = ui.button(tr("Cancel"));
|
||||
app.auto.add("dialog.primitive.cancel", c.rect, "Cancel");
|
||||
close |= c.clicked();
|
||||
});
|
||||
});
|
||||
app.dialog_state.primitive = d.clone();
|
||||
if ok || super::dialogs::ui_enter(ctx) {
|
||||
let mut p = json!({"kind": d.kind.label().to_ascii_lowercase(), "name": d.name});
|
||||
for dim in dimensions(d.kind) {
|
||||
if let Some(v) = d.dims.get(&dim.match_id) {
|
||||
p[dim.match_id.as_str()] = json!(v);
|
||||
}
|
||||
}
|
||||
if let Err(e) = app.session.execute("layer.newPrimitive", p) {
|
||||
app.ui.status = e.to_string();
|
||||
}
|
||||
close = true;
|
||||
}
|
||||
if close {
|
||||
app.dialog = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Menu routing: New 3D Primitive without parameters opens the dialog. Returns true when it did.
|
||||
pub fn route(app: &mut EffectcraftApp, id: &str, params: &Value) -> Result<bool, String> {
|
||||
if id != "layer.newPrimitive" || !params.as_object().is_none_or(|m| m.is_empty()) {
|
||||
return Ok(false);
|
||||
}
|
||||
open(app).map(|_| true)
|
||||
}
|
||||
Reference in New Issue
Block a user