重构 UI 编辑器数据结构和模块设计

- 修改资源模块的类型定义和导入路径,使结构更清晰
- 为数据模型扩展更多元数据字段(如 `NodeStatus`, `NodeSource`, 等)以及类型支持
- 引入命令模块,增加 `UIDesignSuggestion` 实体
- 更新 `SpriteBorder` 和相关组件实现,优化错误校验逻辑
This commit is contained in:
2026-08-13 14:09:49 +08:00
parent c51cd973fc
commit 39aabd77fc
15 changed files with 82 additions and 47 deletions
@@ -0,0 +1 @@
mod ui_design_suggestion;
@@ -0,0 +1,9 @@
use crate::ui_editor::resource::ui_design_image::UIDesignImageRole;
use crate::ui_editor::utils::UIDesignImageId;
pub struct UIDesignSuggestion {
ui_design_image_id: UIDesignImageId,
description: Option<String>,
role: Option<UIDesignImageRole>,
slave_to: Option<UIDesignImageId>,
}
@@ -7,6 +7,7 @@ use nalgebra::Vector2;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use typed_floats::tf32::StrictlyPositiveFinite;
use crate::ui_editor::utils::SpriteAssetId;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, TS)]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))]
@@ -165,6 +166,7 @@ impl Default for ImageType {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SetNativeSizeError {
MissingSprite,
MismatchedSprite,
UnsupportedImageType,
}
@@ -172,6 +174,7 @@ impl fmt::Display for SetNativeSizeError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
Self::MissingSprite => "Image 没有 Target Graphic Sprite",
Self::MismatchedSprite => "提供的 Sprite 与 Image Target Graphic 不匹配",
Self::UnsupportedImageType => "只有 Simple 或 Filled Image 支持 Set Native Size",
};
formatter.write_str(message)
@@ -184,7 +187,7 @@ impl Error for SetNativeSizeError {}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TS)]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))]
pub struct ImageComponent {
pub target_graphic: Option<SpriteAsset>,
pub target_graphic: Option<SpriteAssetId>,
pub image_type: ImageType,
}
@@ -199,16 +202,20 @@ impl ImageComponent {
/// 跨组件把布局重置为 Sprite 的原始尺寸。
pub fn set_native_size(
&self,
sprite: &SpriteAsset,
control: &mut Transform,
) -> Result<Vector2<f32>, SetNativeSizeError> {
if !self.image_type.supports_native_size() {
return Err(SetNativeSizeError::UnsupportedImageType);
}
let native_size = self
let target_graphic = self
.target_graphic
.as_ref()
.ok_or(SetNativeSizeError::MissingSprite)?
.native_size();
.ok_or(SetNativeSizeError::MissingSprite)?;
if target_graphic != sprite.asset_id() {
return Err(SetNativeSizeError::MismatchedSprite);
}
let native_size = sprite.native_size();
control.anchor_max = control.anchor_min;
control.offset_max = control.offset_min + native_size;
Ok(native_size)
@@ -1,10 +1,10 @@
use crate::ui_editor::component::common::error::ComponentValueError;
use crate::ui_editor::resource::asset_id::FontAssetId;
use crate::ui_editor::resource::sprite::{Color, WHITE};
use serde::{Deserialize, Serialize};
use std::num::NonZeroU32;
use ts_rs::TS;
use typed_floats::tf32::StrictlyPositiveFinite;
use crate::ui_editor::utils::FontAssetId;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, TS)]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))]
@@ -1,22 +1,40 @@
use crate::ui_editor::component::Component;
use crate::ui_editor::layout::transform::Transform;
use crate::ui_editor::utils::NodeId;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TS)]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))]
pub struct Node {
id: NodeId,
transform: Transform,
metadata: NodeMetadata,
components: Vec<Component>,
children: Vec<Node>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TS)]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))]
pub enum NodeSource {
Human,
Llm,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TS)]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))]
pub enum NodeStatus {
Passed,
NeedReview(String), // reason inside
Blocked,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TS)]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))]
pub struct NodeMetadata {
name: String,
description: String,
// TODO
from: String,
status: NodeStatus,
source: NodeSource,
}
@@ -1,6 +1,6 @@
pub mod component;
pub mod layout;
pub mod resource;
pub mod agent;
pub mod state;
pub mod commands;
mod utils;
@@ -1,6 +1,6 @@
use crate::ui_editor::resource::asset_id::FontAssetId;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::ui_editor::utils::FontAssetId;
// TODO
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TS)]
@@ -1,4 +1,3 @@
pub mod asset_id;
pub mod font;
pub mod sprite;
pub mod ui_design_image;
@@ -1,9 +1,9 @@
use crate::ui_editor::component::common::error::ComponentValueError;
use crate::ui_editor::resource::asset_id::SpriteAssetId;
use nalgebra::Vector2;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use typed_floats::tf32::StrictlyPositiveFinite;
use crate::ui_editor::utils::SpriteAssetId;
pub type Color = [u8; 4];
@@ -12,49 +12,42 @@ pub(in crate::ui_editor) const WHITE: Color = [255, 255, 255, 255];
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, TS)]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))]
pub struct SpriteBorder {
left: f32,
right: f32,
top: f32,
bottom: f32,
left: u32,
right: u32,
top: u32,
bottom: u32,
}
impl SpriteBorder {
pub const NONE: Self = Self {
left: 0.0,
right: 0.0,
top: 0.0,
bottom: 0.0,
left: 0,
right: 0,
top: 0,
bottom: 0,
};
pub fn new(left: f32, right: f32, top: f32, bottom: f32) -> Result<Self, ComponentValueError> {
let values = [left, right, top, bottom];
if values.iter().any(|value| !value.is_finite()) {
return Err(ComponentValueError::NonFinite);
}
if values.iter().any(|value| *value < 0.0) {
return Err(ComponentValueError::MustBeNonNegative);
}
Ok(Self {
pub const fn new(left: u32, right: u32, top: u32, bottom: u32) -> Self {
Self {
left,
right,
top,
bottom,
})
}
}
pub fn left(self) -> f32 {
pub fn left(self) -> u32 {
self.left
}
pub fn right(self) -> f32 {
pub fn right(self) -> u32 {
self.right
}
pub fn top(self) -> f32 {
pub fn top(self) -> u32 {
self.top
}
pub fn bottom(self) -> f32 {
pub fn bottom(self) -> u32 {
self.bottom
}
@@ -103,7 +96,12 @@ impl SpriteAsset {
if pixel_size.x <= 0.0 || pixel_size.y <= 0.0 {
return Err(ComponentValueError::MustBePositive);
}
if border.left + border.right > pixel_size.x || border.top + border.bottom > pixel_size.y {
let available_width = f64::from(pixel_size.x.floor());
let available_height = f64::from(pixel_size.y.floor());
if border.has_border()
&& (f64::from(border.left) + f64::from(border.right) + 1.0 > available_width
|| f64::from(border.top) + f64::from(border.bottom) + 1.0 > available_height)
{
return Err(ComponentValueError::SpriteBorderExceedsSprite);
}
Ok(Self {
@@ -1,8 +1,8 @@
use nalgebra::Vector2;
use crate::ui_editor::resource::asset_id::UIDesignImageId;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use typed_floats::tf32::StrictlyPositiveFinite;
use crate::ui_editor::utils::UIDesignImageId;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, TS)]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))]
@@ -20,6 +20,7 @@ pub enum UIDesignImageRole {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TS)]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))]
pub struct UIDesignImage {
metadata: UIDesignImageMetadata,
path: String,
#[ts(as = "[f32; 2]")]
pixel_size: Vector2<f32>,
@@ -30,6 +31,7 @@ pub struct UIDesignImage {
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))]
pub struct UIDesignImageMetadata {
name: String,
description: String,
role: Option<UIDesignImageRole>,
slave_to: UIDesignImageId,
slave_to: Option<UIDesignImageId>,
}
@@ -1,11 +1,11 @@
use crate::ui_editor::layout::node::Node;
use crate::ui_editor::resource::asset_id::{FontAssetId, SpriteAssetId, UIDesignImageId};
use crate::ui_editor::resource::font::FontAsset;
use crate::ui_editor::resource::sprite::SpriteAsset;
use crate::ui_editor::resource::ui_design_image::UIDesignImage;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use ts_rs::TS;
use crate::ui_editor::utils::{FontAssetId, SpriteAssetId, UIDesignImageId};
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TS)]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))]
@@ -1,6 +1,6 @@
use crate::ui_editor::component::common::error::ComponentValueError;
macro_rules! define_asset_id {
macro_rules! define_id {
($name:ident) => {
#[derive(
Clone,
@@ -38,7 +38,7 @@ macro_rules! define_asset_id {
}
};
}
define_asset_id!(FontAssetId);
define_asset_id!(SpriteAssetId);
define_asset_id!(UIDesignImageId);
define_id!(FontAssetId);
define_id!(SpriteAssetId);
define_id!(UIDesignImageId);
define_id!(NodeId);
@@ -1,8 +1,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ImageType } from "./ImageType";
import type { SpriteAsset } from "./SpriteAsset";
import type { SpriteAssetId } from "./SpriteAssetId";
/**
* Image 渲染组件;布局由同一 Entity 上独立的 `Control` 提供。
*/
export type ImageComponent = { target_graphic: SpriteAsset | null, image_type: ImageType, };
export type ImageComponent = { target_graphic: SpriteAssetId | null, image_type: ImageType, };
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UIDesignImageMetadata } from "./UIDesignImageMetadata";
export type UIDesignImage = { path: string, pixel_size: [number, number], pixels_per_unit: number, };
export type UIDesignImage = { metadata: UIDesignImageMetadata, path: string, pixel_size: [number, number], pixels_per_unit: number, };
@@ -2,4 +2,4 @@
import type { UIDesignImageId } from "./UIDesignImageId";
import type { UIDesignImageRole } from "./UIDesignImageRole";
export type UIDesignImageMetadata = { name: string, role: UIDesignImageRole | null, slave_to: UIDesignImageId, };
export type UIDesignImageMetadata = { name: string, role: UIDesignImageRole | null, slave_to: UIDesignImageId | null, };