ソースを参照

完善toolkit库 (#10)

R0ronoa 1 年間 前
コミット
b0262857c5

+ 36 - 34
starry_applications/src/asset_manager/code/asset_item.rs

@@ -1,13 +1,18 @@
 use std::{
     cell::{Cell, RefCell},
+    str::FromStr,
     sync::Arc,
 };
 
 use starry_client::base::{color::Color, renderer::Renderer};
 use starry_toolkit::{
-    base::{point::Point, rect::Rect},
-    traits::{text::Text, transform::Transform},
-    widgets::{image::Image, label::Label, HorizontalPlacement, VerticalPlacement, Widget},
+    base::{rect::Rect, vector2::Vector2},
+    traits::text::Text,
+    widgets::{
+        image::Image,
+        label::{Label, LabelOverflowType},
+        widget_add_child, PivotType, Widget,
+    },
 };
 
 use crate::starry_server::base::image::Image as ImageResource;
@@ -15,38 +20,36 @@ use crate::starry_server::base::image::Image as ImageResource;
 const FILE_ICON_PATH: &[u8] = include_bytes!("../resource/file_icon.png");
 const DIR_ICON_PATH: &[u8] = include_bytes!("../resource/dir_icon.png");
 
-pub enum AssetType {
-    Folder,
-    File,
-}
-
 pub struct AssetItem {
     pub rect: Cell<Rect>,
-    local_position: Cell<Point>,
-    vertical_placement: Cell<VerticalPlacement>,
-    horizontal_placement: Cell<HorizontalPlacement>,
+    pivot: Cell<PivotType>,
+    pivot_offset: Cell<Vector2>,
+    parent: RefCell<Option<Arc<dyn Widget>>>,
     children: RefCell<Vec<Arc<dyn Widget>>>,
     /// 缓存值
     cache_focused: Cell<bool>,
+    _file_path: RefCell<String>,
 }
 
 impl AssetItem {
     pub const ITEM_WIDTH: u32 = 144;
     pub const ITEM_HEIGHT: u32 = 144;
 
-    pub fn new(file_name: &str, is_dir: bool) -> Self {
-        let item = AssetItem {
+    pub fn new(file_name: &str, is_dir: bool) -> Arc<Self> {
+        let item = Arc::new(AssetItem {
             rect: Cell::new(Rect::new(0, 0, Self::ITEM_WIDTH, Self::ITEM_HEIGHT)),
-            local_position: Cell::new(Point::new(0, 0)),
-            vertical_placement: Cell::new(VerticalPlacement::Absolute),
-            horizontal_placement: Cell::new(HorizontalPlacement::Absolute),
+            pivot: Cell::new(PivotType::TopLeft),
+            pivot_offset: Cell::new(Vector2::new(0, 0)),
+            parent: RefCell::new(None),
             children: RefCell::new(Vec::new()),
             cache_focused: Cell::new(false),
-        };
+            _file_path: RefCell::new(String::from_str(file_name).unwrap()),
+        });
 
         // 背景Image
-        let bg = Image::from_color(160, 160, Color::rgba(0, 0, 0, 0));
-        item.add_child(bg);
+        let bg = Image::from_color(Self::ITEM_WIDTH, Self::ITEM_HEIGHT, Color::rgba(0, 0, 0, 0));
+        bg.set_pivot_type(PivotType::Center);
+        widget_add_child(item.clone(), bg.clone());
 
         // 文件图标Image
         if let Some(icon) = match is_dir {
@@ -54,24 +57,23 @@ impl AssetItem {
             false => ImageResource::from_path(FILE_ICON_PATH),
         } {
             let icon = Image::from_image(icon);
-            icon.horizontal_placement().set(HorizontalPlacement::Center);
-            icon.vertical_placement().set(VerticalPlacement::Top);
-            item.add_child(icon);
+            icon.set_pivot_type(PivotType::Top);
+            widget_add_child(item.clone(), icon.clone());
         }
 
         // 文件名Label
         let name = Label::new();
-        name.text(file_name);
-        name.horizontal_placement().set(HorizontalPlacement::Center);
-        name.vertical_placement().set(VerticalPlacement::Bottom);
-        item.add_child(name);
+        name.set_adapt_type(LabelOverflowType::Omit);
+        name.resize(Self::ITEM_WIDTH, 16);
+        name.set_text(file_name);
+        name.set_pivot_type(PivotType::Bottom);
+        name.set_pivot_offset(Vector2::new(0, -4));
+        widget_add_child(item.clone(), name.clone());
 
         return item;
     }
 }
 
-impl Transform for AssetItem {}
-
 impl Widget for AssetItem {
     fn name(&self) -> &str {
         "AssetItem"
@@ -81,16 +83,16 @@ impl Widget for AssetItem {
         &self.rect
     }
 
-    fn vertical_placement(&self) -> &Cell<VerticalPlacement> {
-        &self.vertical_placement
+    fn pivot(&self) -> &Cell<PivotType> {
+        &self.pivot
     }
 
-    fn horizontal_placement(&self) -> &Cell<HorizontalPlacement> {
-        &self.horizontal_placement
+    fn pivot_offset(&self) -> &Cell<Vector2> {
+        &self.pivot_offset
     }
 
-    fn local_position(&self) -> &Cell<Point> {
-        &self.local_position
+    fn parent(&self) -> &RefCell<Option<Arc<dyn Widget>>> {
+        &self.parent
     }
 
     fn children(&self) -> &RefCell<Vec<Arc<dyn Widget>>> {

+ 8 - 8
starry_applications/src/asset_manager/code/mod.rs

@@ -1,15 +1,14 @@
 use crate::starry_toolkit::traits::focus::Focus;
+use starry_client::base::color::Color;
 use starry_server::core::{SCREEN_HEIGHT, SCREEN_WIDTH};
 use starry_toolkit::{
     base::{panel::Panel, rect::Rect},
-    layout::grid::Grid,
-    traits::transform::Transform,
-    widgets::image::Image,
+    layout::grid::{Grid, GridArrangeType},
+    widgets::{image::Image, widget_add_child},
 };
 use std::{cell::RefCell, fs, sync::Arc};
 
 use self::asset_item::AssetItem;
-
 use crate::starry_server::base::image::Image as ImageResource;
 
 pub mod asset_item;
@@ -31,10 +30,9 @@ impl AssetViewer {
 
     pub fn init(&self) {
         let grid = self.asset_grid.borrow();
-        grid.resize(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32);
-        grid.reposition(0, 0);
-        grid.set_max_columns(5);
+        grid.set_upper_limit(5);
         grid.set_space(20, 20);
+        grid.set_arrange_type(GridArrangeType::Horizontal);
     }
 
     pub fn refresh(&self) {
@@ -46,7 +44,8 @@ impl AssetViewer {
                         item.file_name().to_str().unwrap(),
                         item.metadata().unwrap().is_dir(),
                     );
-                    self.asset_grid.borrow_mut().add(&Arc::new(item));
+                    self.asset_grid.borrow_mut().add(&item);
+                    widget_add_child(self.asset_grid.borrow().clone(), item);
                 }
             }
         }
@@ -64,6 +63,7 @@ impl AssetViewer {
         let panel = Panel::new(
             Rect::new(0, 0, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32),
             "Title",
+            Color::rgb(0, 0, 0),
         );
 
         panel.add_child(&Image::from_image(

+ 2 - 2
starry_client/src/window.rs

@@ -86,7 +86,7 @@ impl Renderer for Window {
 #[allow(dead_code)]
 impl Window {
     /// TODO: 接收flags
-    pub fn new(x: i32, y: i32, w: u32, h: u32, title: &str) -> Self {
+    pub fn new(x: i32, y: i32, w: u32, h: u32, title: &str, color: Color) -> Self {
         Window {
             x: x,
             y: y,
@@ -97,7 +97,7 @@ impl Window {
             resizable: false,
             mode: Cell::new(RenderMode::Blend),
             // file_opt: None,
-            data_opt: Some(vec![Color::rgb(0, 0, 0); (w * h) as usize].into_boxed_slice()),
+            data_opt: Some(vec![color; (w * h) as usize].into_boxed_slice()),
         }
 
         // TODO: 与服务器通信

+ 1 - 0
starry_server/src/core/mod.rs

@@ -19,6 +19,7 @@ pub mod compositor;
 pub mod input;
 pub mod window_manager;
 
+// TODO: 读帧缓冲设备属性
 /// 屏幕宽度
 pub const SCREEN_WIDTH: usize = 1440;
 /// 屏幕高度

+ 1 - 1
starry_toolkit/src/base/mod.rs

@@ -1,3 +1,3 @@
 pub mod panel;
-pub mod point;
 pub mod rect;
+pub mod vector2;

+ 9 - 2
starry_toolkit/src/base/panel.rs

@@ -77,8 +77,15 @@ pub struct Panel {
 }
 
 impl Panel {
-    pub fn new(rect: Rect, title: &str) -> Self {
-        Panel::from_window(Window::new(rect.x, rect.y, rect.width, rect.height, title))
+    pub fn new(rect: Rect, title: &str, color: Color) -> Self {
+        Panel::from_window(Window::new(
+            rect.x,
+            rect.y,
+            rect.width,
+            rect.height,
+            title,
+            color,
+        ))
     }
 
     pub fn from_window(window: Window) -> Self {

+ 43 - 8
starry_toolkit/src/base/rect.rs

@@ -1,11 +1,11 @@
-use super::point::Point;
+use super::vector2::Vector2;
 
 /// 表示一个矩形区域
 #[derive(Clone, Copy, Debug, Default)]
 pub struct Rect {
-    /// 左上角x坐标
+    /// 左上角x坐标(世界坐标)
     pub x: i32,
-    /// 左上角y坐标
+    /// 左上角y坐标(世界坐标)
     pub y: i32,
     /// 矩形宽度
     pub width: u32,
@@ -24,12 +24,47 @@ impl Rect {
     }
 
     /// 返回矩形左上角的位置点
-    pub fn point(&self) -> Point {
-        Point::new(self.x, self.y)
+    pub fn top_left_pos(&self) -> Vector2 {
+        Vector2::new(self.x, self.y)
+    }
+
+    pub fn top_pos(&self) -> Vector2 {
+        Vector2::new(self.x + self.width as i32 / 2, self.y)
+    }
+
+    pub fn top_right_pos(&self) -> Vector2 {
+        Vector2::new(self.x + self.width as i32, self.y)
+    }
+
+    pub fn left_pos(&self) -> Vector2 {
+        Vector2::new(self.x, self.y + self.height as i32 / 2)
+    }
+
+    pub fn center_pos(&self) -> Vector2 {
+        Vector2::new(
+            self.x + self.width as i32 / 2,
+            self.y + self.height as i32 / 2,
+        )
+    }
+
+    pub fn right_pos(&self) -> Vector2 {
+        Vector2::new(self.x + self.width as i32, self.y + self.height as i32 / 2)
+    }
+
+    pub fn bottom_left_pos(&self) -> Vector2 {
+        Vector2::new(self.x, self.y + self.height as i32)
+    }
+
+    pub fn bottom_pos(&self) -> Vector2 {
+        Vector2::new(self.x + self.width as i32 / 2, self.y + self.height as i32)
+    }
+
+    pub fn bottom_right_pos(&self) -> Vector2 {
+        Vector2::new(self.x + self.width as i32, self.y + self.height as i32)
     }
 
     /// 判断该矩形是否包含某点
-    pub fn contains(&self, p: Point) -> bool {
+    pub fn contains(&self, p: Vector2) -> bool {
         p.x >= self.x
             && p.x < self.x + self.width as i32
             && p.y >= self.y
@@ -38,8 +73,8 @@ impl Rect {
 
     /// 判断该矩形是否完全包含另一个矩形
     pub fn contains_rect(&self, r: &Rect) -> bool {
-        let p1 = r.point();
-        let p2 = p1 + Point::new(r.width as i32, r.height as i32);
+        let p1 = r.top_left_pos();
+        let p2 = p1 + Vector2::new(r.width as i32, r.height as i32);
         self.contains(p1) && self.contains(p2)
     }
 

+ 12 - 12
starry_toolkit/src/base/point.rs → starry_toolkit/src/base/vector2.rs

@@ -1,36 +1,36 @@
 use std::ops::{Add, Sub};
 
-/// 表示一个位置点
+/// 二维向量
 #[derive(Copy, Clone, Debug, Default)]
-pub struct Point {
+pub struct Vector2 {
     /// x坐标
     pub x: i32,
     /// y坐标
     pub y: i32,
 }
 
-impl Point {
+impl Vector2 {
     pub fn new(x: i32, y: i32) -> Self {
-        Point { x: x, y: y }
+        Vector2 { x: x, y: y }
     }
 }
 
-impl Add for Point {
-    type Output = Point;
+impl Add for Vector2 {
+    type Output = Vector2;
 
-    fn add(self, other: Point) -> Self::Output {
-        Point {
+    fn add(self, other: Vector2) -> Self::Output {
+        Vector2 {
             x: self.x + other.x,
             y: self.y + other.y,
         }
     }
 }
 
-impl Sub for Point {
-    type Output = Point;
+impl Sub for Vector2 {
+    type Output = Vector2;
 
-    fn sub(self, other: Point) -> Self::Output {
-        Point {
+    fn sub(self, other: Vector2) -> Self::Output {
+        Vector2 {
             x: self.x - other.x,
             y: self.y - other.y,
         }

+ 91 - 55
starry_toolkit/src/layout/grid.rs

@@ -7,23 +7,32 @@ use std::{
 use starry_client::base::renderer::Renderer;
 
 use crate::{
-    base::{point::Point, rect::Rect},
-    traits::{focus::Focus, transform::Transform},
-    widgets::{HorizontalPlacement, VerticalPlacement, Widget},
+    base::{rect::Rect, vector2::Vector2},
+    traits::focus::Focus,
+    widgets::{PivotType, Widget},
 };
 
+/// 网格排列方式
+#[derive(PartialEq, Copy, Clone)]
+pub enum GridArrangeType {
+    /// 优先横向排列
+    Horizontal,
+    /// 优先纵向排列
+    Vertical,
+}
+
 pub struct Grid {
     pub rect: Cell<Rect>,
-    local_position: Cell<Point>,
-    vertical_placement: Cell<VerticalPlacement>,
-    horizontal_placement: Cell<HorizontalPlacement>,
+    pivot: Cell<PivotType>,
+    pivot_offset: Cell<Vector2>,
     children: RefCell<Vec<Arc<dyn Widget>>>,
+    parent: RefCell<Option<Arc<dyn Widget>>>,
     /// x坐标间隔
     space_x: Cell<i32>,
     /// y坐标间隔
     space_y: Cell<i32>,
-    /// 每行的最大列
-    max_columns: Cell<usize>,
+    /// 每行/列的最大元素
+    upper_limit: Cell<usize>,
     /// 当前行数
     current_row: Cell<usize>,
     /// 当前列数
@@ -34,45 +43,78 @@ pub struct Grid {
     pub focused_id: Cell<Option<(usize, usize)>>,
     /// 当前聚焦的widget
     pub focused_widget: RefCell<Option<Arc<dyn Widget>>>,
+    /// 优先排列方式
+    arrange_type: Cell<GridArrangeType>,
 }
 
 impl Grid {
     pub fn new() -> Arc<Self> {
         Arc::new(Grid {
             rect: Cell::new(Rect::default()),
-            local_position: Cell::new(Point::new(0, 0)),
-            vertical_placement: Cell::new(VerticalPlacement::Absolute),
-            horizontal_placement: Cell::new(HorizontalPlacement::Absolute),
+            pivot: Cell::new(PivotType::TopLeft),
+            pivot_offset: Cell::new(Vector2::new(0, 0)),
             children: RefCell::new(vec![]),
+            parent: RefCell::new(None),
             space_x: Cell::new(0),
             space_y: Cell::new(0),
-            max_columns: Cell::new(0),
+            upper_limit: Cell::new(0),
             current_row: Cell::new(0),
             current_column: Cell::new(0),
             elements: RefCell::new(BTreeMap::new()),
             focused_id: Cell::new(None),
             focused_widget: RefCell::new(None),
+            arrange_type: Cell::new(GridArrangeType::Vertical),
         })
     }
 
-    /// 设置最大列数
-    pub fn set_max_columns(&self, columns: usize) -> &Self {
-        self.max_columns.set(columns);
+    /// 设置每行/列最大元素数量(取决于行/列优先排列)
+    pub fn set_upper_limit(&self, columns: usize) -> &Self {
+        self.upper_limit.set(columns);
         self
     }
 
-    pub fn add<T: Widget>(&self, element: &Arc<T>) {
-        if self.current_column.get() == self.max_columns.get() {
-            self.current_row.set(self.current_row.get() + 1);
-            self.current_column.set(0);
-        }
+    pub fn set_arrange_type(&self, arrange_type: GridArrangeType) -> &Self {
+        self.arrange_type.set(arrange_type);
+        self
+    }
 
+    pub fn add<T: Widget>(&self, element: &Arc<T>) {
+        self.find_next_slot();
         self.elements.borrow_mut().insert(
             (self.current_row.get(), self.current_column.get()),
             element.clone(),
         );
-        self.current_column.set(self.current_column.get() + 1);
-        self.arrange(false);
+        self.move_index();
+        self.arrange_elements(false);
+    }
+
+    /// 找到下一个可放置元素的位置
+    fn find_next_slot(&self) {
+        let elements = self.elements.borrow();
+        while elements.contains_key(&(self.current_row.get(), self.current_column.get())) {
+            self.move_index();
+        }
+    }
+
+    fn move_index(&self) {
+        match self.arrange_type.get() {
+            GridArrangeType::Horizontal => {
+                self.current_column.set(self.current_column.get() + 1);
+
+                if self.current_column.get() == self.upper_limit.get() {
+                    self.current_row.set(self.current_row.get() + 1);
+                    self.current_column.set(0);
+                }
+            }
+            GridArrangeType::Vertical => {
+                self.current_row.set(self.current_row.get() + 1);
+
+                if self.current_row.get() == self.upper_limit.get() {
+                    self.current_column.set(self.current_column.get() + 1);
+                    self.current_row.set(0);
+                }
+            }
+        }
     }
 
     pub fn insert<T: Widget>(&self, column: usize, row: usize, element: &Arc<T>) {
@@ -80,7 +122,7 @@ impl Grid {
             .borrow_mut()
             .insert((row, column), element.clone());
 
-        self.arrange(false);
+        self.arrange_elements(false);
     }
 
     pub fn clear(&self) {
@@ -97,10 +139,17 @@ impl Grid {
         self
     }
 
-    pub fn arrange(&self, resize_children: bool) {
+    // TODO 注释补充
+    pub fn arrange_elements(&self, resize_children: bool) {
+        if self.elements.borrow().is_empty() {
+            return;
+        }
+
+        self.arrange_self();
+
         let mut cols = Vec::new();
         let mut rows = Vec::new();
-        for (&(col, row), entry) in self.elements.borrow().iter() {
+        for (&(row, col), entry) in self.elements.borrow().iter() {
             while col >= cols.len() {
                 cols.push(Rect::default());
             }
@@ -122,33 +171,33 @@ impl Grid {
             }
         }
 
-        let rect = self.rect.get();
         let space_x = self.space_x.get();
         let space_y = self.space_y.get();
 
-        let mut x = rect.x;
+        let mut x = 0;
         for col in cols.iter_mut() {
             col.x = x;
             x += col.width as i32 + space_x;
         }
 
-        let mut y = rect.y;
+        let mut y = 0;
         for row in rows.iter_mut() {
             row.y = y;
             y += row.height as i32 + space_y;
         }
 
-        for (&(col, row), child) in self.elements.borrow().iter() {
-            let mut rect = child.rect().get();
-            rect.x = cols[col].x;
-            rect.y = rows[row].y;
+        let grid_width = cols.len() as i32 * (cols[0].width as i32 + space_x) - space_x;
+        let grid_height = rows.len() as i32 * (rows[0].width as i32 + space_y) - space_y;
+        self.resize(grid_width as u32, grid_height as u32);
+
+        for (&(row, col), child) in self.elements.borrow().iter() {
+            child.set_pivot_type(PivotType::TopLeft);
+            child.set_pivot_offset(Vector2::new(cols[col].x, rows[row].y));
             if resize_children {
-                rect.width = cols[col].width;
-                rect.height = rows[row].height;
+                child.resize(cols[col].width, rows[row].height);
             }
-            child.rect().set(rect);
 
-            child.arrange();
+            child.arrange_all();
         }
     }
 }
@@ -162,16 +211,16 @@ impl Widget for Grid {
         &self.rect
     }
 
-    fn local_position(&self) -> &Cell<Point> {
-        &self.local_position
+    fn pivot(&self) -> &Cell<PivotType> {
+        &self.pivot
     }
 
-    fn vertical_placement(&self) -> &Cell<VerticalPlacement> {
-        &self.vertical_placement
+    fn pivot_offset(&self) -> &Cell<Vector2> {
+        &self.pivot_offset
     }
 
-    fn horizontal_placement(&self) -> &Cell<HorizontalPlacement> {
-        &self.horizontal_placement
+    fn parent(&self) -> &RefCell<Option<Arc<dyn Widget>>> {
+        &self.parent
     }
 
     fn children(&self) -> &RefCell<Vec<Arc<dyn Widget>>> {
@@ -194,19 +243,6 @@ impl Widget for Grid {
     }
 }
 
-impl Transform for Grid {
-    fn reposition(&self, x: i32, y: i32) -> &Self {
-        let mut rect = self.rect().get();
-        rect.x = x;
-        rect.y = y;
-        self.rect.set(rect);
-
-        self.arrange(false);
-
-        self
-    }
-}
-
 impl Focus for Grid {
     fn focused_widget(&self) -> RefCell<Option<Arc<dyn Widget>>> {
         self.focused_widget.clone()

+ 7 - 6
starry_toolkit/src/main.rs

@@ -1,8 +1,9 @@
+use starry_client::base::color::Color;
 use starry_server::core::{SCREEN_HEIGHT, SCREEN_WIDTH};
 use starry_toolkit::{
     base::{panel::Panel, rect::Rect},
     layout::grid::Grid,
-    traits::{text::Text, transform::Transform},
+    traits::text::Text,
     widgets::label::Label,
 };
 
@@ -10,21 +11,21 @@ fn main() {
     let panel = Panel::new(
         Rect::new(0, 0, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32),
         "Title",
+        Color::rgb(255, 255, 255),
     );
 
     let label1 = Label::new();
-    label1.text("hello world");
+    label1.set_text("abc");
 
     let label2 = Label::new();
-    label2.text("hello world");
+    label2.set_text("....");
 
     let label3 = Label::new();
-    label3.text("hello world");
+    label3.set_text("12.g");
 
     let grid = Grid::new();
     grid.set_space(10, 10);
-    grid.resize(500, 500);
-    grid.set_max_columns(2);
+    grid.set_upper_limit(2);
     grid.add(&label1);
     grid.add(&label2);
     grid.add(&label3);

+ 5 - 2
starry_toolkit/src/traits/text.rs

@@ -1,4 +1,7 @@
+use starry_client::base::color::Color;
+
 pub trait Text {
-    fn text<S: Into<String>>(&self, text: S) -> &Self;
-    fn text_offset(&self, x: i32, y: i32) -> &Self;
+    fn set_text<S: Into<String>>(&self, text: S) -> &Self;
+
+    fn set_text_color(&self, color: Color) -> &Self;
 }

+ 0 - 35
starry_toolkit/src/traits/transform.rs

@@ -1,36 +1 @@
-use crate::widgets::{HorizontalPlacement, VerticalPlacement, Widget};
 
-pub trait Transform: Sized + Widget {
-    fn reposition(&self, x: i32, y: i32) -> &Self {
-        let mut position = self.local_position().get();
-        let mut rect = self.rect().get();
-        position.x = x;
-        position.y = y;
-        rect.x = x;
-        rect.y = y;
-        self.local_position().set(position);
-        self.rect().set(rect);
-        self.arrange();
-        self
-    }
-
-    fn resize(&self, width: u32, height: u32) -> &Self {
-        let mut rect = self.rect().get();
-        rect.width = width;
-        rect.height = height;
-        self.rect().set(rect);
-        self.arrange();
-        self
-    }
-
-    fn placement(
-        &self,
-        vertical_placement: VerticalPlacement,
-        horizontal_placement: HorizontalPlacement,
-    ) -> &Self {
-        self.vertical_placement().set(vertical_placement);
-        self.horizontal_placement().set(horizontal_placement);
-        self.arrange();
-        self
-    }
-}

+ 14 - 19
starry_toolkit/src/widgets/image.rs

@@ -5,21 +5,18 @@ use std::{
 
 use starry_client::base::{color::Color, renderer::Renderer};
 
-use crate::{
-    base::{point::Point, rect::Rect},
-    traits::transform::Transform,
-};
+use crate::base::{rect::Rect, vector2::Vector2};
 
-use super::{HorizontalPlacement, VerticalPlacement, Widget};
+use super::{PivotType, Widget};
 
 use crate::starry_server::base::image::Image as ImageResource;
 
 pub struct Image {
     pub rect: Cell<Rect>,
-    local_position: Cell<Point>,
-    vertical_placement: Cell<VerticalPlacement>,
-    horizontal_placement: Cell<HorizontalPlacement>,
+    pivot: Cell<PivotType>,
+    pivot_offset: Cell<Vector2>,
     children: RefCell<Vec<Arc<dyn Widget>>>,
+    parent: RefCell<Option<Arc<dyn Widget>>>,
     /// 图像源数据
     pub image: RefCell<ImageResource>,
 }
@@ -40,9 +37,9 @@ impl Image {
     pub fn from_image(image: ImageResource) -> Arc<Self> {
         Arc::new(Image {
             rect: Cell::new(Rect::new(0, 0, image.width() as u32, image.height() as u32)),
-            local_position: Cell::new(Point::new(0, 0)),
-            vertical_placement: Cell::new(VerticalPlacement::Absolute),
-            horizontal_placement: Cell::new(HorizontalPlacement::Absolute),
+            pivot: Cell::new(PivotType::TopLeft),
+            pivot_offset: Cell::new(Vector2::new(0, 0)),
+            parent: RefCell::new(None),
             children: RefCell::new(vec![]),
             image: RefCell::new(image),
         })
@@ -57,8 +54,6 @@ impl Image {
     }
 }
 
-impl Transform for Image {}
-
 impl Widget for Image {
     fn name(&self) -> &str {
         "Image"
@@ -68,16 +63,16 @@ impl Widget for Image {
         &self.rect
     }
 
-    fn vertical_placement(&self) -> &Cell<VerticalPlacement> {
-        &self.vertical_placement
+    fn pivot(&self) -> &Cell<PivotType> {
+        &self.pivot
     }
 
-    fn horizontal_placement(&self) -> &Cell<HorizontalPlacement> {
-        &self.horizontal_placement
+    fn pivot_offset(&self) -> &Cell<Vector2> {
+        &self.pivot_offset
     }
 
-    fn local_position(&self) -> &Cell<Point> {
-        &self.local_position
+    fn parent(&self) -> &RefCell<Option<Arc<dyn Widget>>> {
+        &self.parent
     }
 
     fn children(&self) -> &RefCell<Vec<Arc<dyn Widget>>> {

+ 144 - 41
starry_toolkit/src/widgets/label.rs

@@ -1,50 +1,100 @@
 use std::{
     cell::{Cell, RefCell},
+    cmp::max,
     sync::Arc,
 };
 
 use starry_client::base::{color::Color, renderer::Renderer};
 
 use crate::{
-    base::{point::Point, rect::Rect},
-    traits::{text::Text, transform::Transform},
+    base::{rect::Rect, vector2::Vector2},
+    traits::text::Text,
 };
 
-use super::{HorizontalPlacement, VerticalPlacement, Widget};
+use super::{align_rect, PivotType, Widget};
+
+#[derive(PartialEq, Copy, Clone)]
+pub enum LabelOverflowType {
+    /// 不适配 溢出部分不显示
+    None,
+    /// 根据字数调整大小
+    ShinkToFit,
+    /// 省略多余内容
+    Omit,
+    // TODO 支持"调整字体大小以适配"的选项
+}
 
 pub struct Label {
     pub rect: Cell<Rect>,
-    local_position: Cell<Point>,
-    vertical_placement: Cell<VerticalPlacement>,
-    horizontal_placement: Cell<HorizontalPlacement>,
+    pivot: Cell<PivotType>,
+    pivot_offset: Cell<Vector2>,
+    parent: RefCell<Option<Arc<dyn Widget>>>,
     children: RefCell<Vec<Arc<dyn Widget>>>,
-    pub text: RefCell<String>,
-    pub text_offset: Cell<Point>,
+    /// 实际上的文本
+    real_text: RefCell<String>,
+    /// 用于显示的文本
+    show_text: RefCell<String>,
+    text_color: Cell<Color>,
+    adapt_type: Cell<LabelOverflowType>,
+    /// 渲染文本时的矩形区域
+    text_rect: Cell<Rect>,
+    /// 文本在矩形框内的对齐方式
+    text_pivot: Cell<PivotType>,
 }
 
+// TODO 暂时只支持渲染一行字体
 impl Label {
     pub fn new() -> Arc<Self> {
         Arc::new(Label {
             rect: Cell::new(Rect::default()),
-            local_position: Cell::new(Point::new(0, 0)),
-            vertical_placement: Cell::new(VerticalPlacement::Absolute),
-            horizontal_placement: Cell::new(HorizontalPlacement::Absolute),
+            pivot: Cell::new(PivotType::TopLeft),
+            pivot_offset: Cell::new(Vector2::new(0, 0)),
+            parent: RefCell::new(None),
             children: RefCell::new(vec![]),
-            text: RefCell::new(String::new()),
-            text_offset: Cell::new(Point::default()),
+            real_text: RefCell::new(String::new()),
+            show_text: RefCell::new(String::new()),
+            text_color: Cell::new(Color::rgb(0, 0, 0)), // 默认黑色字体
+            adapt_type: Cell::new(LabelOverflowType::None),
+            text_rect: Cell::new(Rect::default()),
+            text_pivot: Cell::new(PivotType::Center),
         })
     }
 
-    fn adjust_size(&self) {
-        let text = self.text.borrow();
-        self.resize(
-            text.len() as u32 * 8 + 2 * self.text_offset.get().x as u32,
-            16 + 2 * self.text_offset.get().y as u32,
-        );
+    /// 处理文本溢出的情况
+    /// 在文本内容改变或大小改变时调用
+    fn handle_overflow(&self) {
+        let text = self.real_text.borrow();
+
+        match self.adapt_type.get() {
+            LabelOverflowType::None => {}
+            LabelOverflowType::ShinkToFit => {
+                self.resize(text.len() as u32 * 8 as u32, 16);
+            }
+            LabelOverflowType::Omit => {
+                let rect = self.rect.get();
+
+                if text.len() as u32 * 8 > rect.width {
+                    let max_count = max(0, (rect.width as i32 - 3 * 8) / 8);
+                    let mut omit_str = self.real_text.borrow().clone();
+                    let _ = omit_str.split_off(max_count as usize);
+                    omit_str.push_str("..."); // 溢出字符用省略号取代
+                    (*self.show_text.borrow_mut()) = omit_str;
+                }
+            }
+        }
+
+        self.text_rect.set(Rect::new(
+            0,
+            0,
+            self.show_text.borrow().len() as u32 * 8,
+            16,
+        ));
     }
-}
 
-impl Transform for Label {}
+    pub fn set_adapt_type(&self, adapt_type: LabelOverflowType) {
+        self.adapt_type.set(adapt_type);
+    }
+}
 
 impl Widget for Label {
     fn name(&self) -> &str {
@@ -55,16 +105,16 @@ impl Widget for Label {
         &self.rect
     }
 
-    fn local_position(&self) -> &Cell<Point> {
-        &self.local_position
+    fn pivot(&self) -> &Cell<PivotType> {
+        &self.pivot
     }
 
-    fn vertical_placement(&self) -> &Cell<VerticalPlacement> {
-        &self.vertical_placement
+    fn pivot_offset(&self) -> &Cell<Vector2> {
+        &self.pivot_offset
     }
 
-    fn horizontal_placement(&self) -> &Cell<HorizontalPlacement> {
-        &self.horizontal_placement
+    fn parent(&self) -> &RefCell<Option<Arc<dyn Widget>>> {
+        &self.parent
     }
 
     fn children(&self) -> &RefCell<Vec<Arc<dyn Widget>>> {
@@ -72,10 +122,15 @@ impl Widget for Label {
     }
 
     fn draw(&self, renderer: &mut dyn Renderer, _focused: bool) {
-        let origin_rect = self.rect().get();
-        let mut current_rect = self.rect().get(); // 当前字符渲染矩形
+        let origin_rect = self.text_rect.get();
+        let mut current_rect = self.text_rect.get(); // 当前字符渲染矩形
         let origin_x = origin_rect.x;
-        let text = self.text.borrow().clone();
+        let text = self.show_text.borrow().clone();
+
+        // 矩形高度不满足
+        if origin_rect.height < 16 {
+            return;
+        }
 
         for char in text.chars() {
             if char == '\n' {
@@ -87,29 +142,77 @@ impl Widget for Label {
                 if current_rect.x + 8 <= origin_rect.x + origin_rect.width as i32
                     && current_rect.y + 16 <= origin_rect.y + origin_rect.height as i32
                 {
-                    // 默认渲染白色字体
                     // TODO 应用主题(Theme)颜色
-                    renderer.char(current_rect.x, current_rect.y, char, Color::rgb(0, 0, 0));
+                    renderer.char(current_rect.x, current_rect.y, char, self.text_color.get());
                 }
                 current_rect.x += 8;
             }
         }
     }
+
+    fn set_pivot_type(&self, pivot_type: PivotType) {
+        self.set_pivot_type_base(pivot_type);
+
+        self.text_rect.set(align_rect(
+            self.text_rect.get(),
+            self.rect.get(),
+            self.text_pivot.get(),
+            Vector2::new(0, 0),
+        ));
+    }
+
+    fn set_pivot_offset(&self, pivot_offset: Vector2) {
+        self.set_pivot_offset_base(pivot_offset);
+
+        self.text_rect.set(align_rect(
+            self.text_rect.get(),
+            self.rect.get(),
+            self.text_pivot.get(),
+            Vector2::new(0, 0),
+        ));
+    }
+
+    fn resize(&self, width: u32, height: u32) {
+        self.resize_base(width, height);
+
+        self.handle_overflow();
+        self.text_rect.set(align_rect(
+            self.text_rect.get(),
+            self.rect.get(),
+            self.text_pivot.get(),
+            Vector2::new(0, 0),
+        ));
+    }
+
+    fn arrange_self(&self) {
+        self.arrange_self_base();
+
+        self.text_rect.set(align_rect(
+            self.text_rect.get(),
+            self.rect.get(),
+            self.text_pivot.get(),
+            Vector2::new(0, 0),
+        ));
+    }
 }
 
 impl Text for Label {
-    fn text<S: Into<String>>(&self, target_text: S) -> &Self {
-        {
-            let mut text = self.text.borrow_mut();
-            *text = target_text.into();
-        }
-        self.adjust_size();
+    fn set_text<S: Into<String>>(&self, text: S) -> &Self {
+        let text = text.into();
+        (*self.real_text.borrow_mut()) = text.clone();
+        (*self.show_text.borrow_mut()) = text;
+        self.handle_overflow();
+        align_rect(
+            self.text_rect.get(),
+            self.rect.get(),
+            self.text_pivot.get(),
+            Vector2::new(0, 0),
+        );
         self
     }
 
-    fn text_offset(&self, x: i32, y: i32) -> &Self {
-        self.text_offset.set(Point::new(x, y));
-        self.adjust_size();
+    fn set_text_color(&self, color: Color) -> &Self {
+        self.text_color.set(color);
         self
     }
 }

+ 147 - 87
starry_toolkit/src/widgets/mod.rs

@@ -5,40 +5,97 @@ use std::{
 };
 
 use starry_client::base::renderer::Renderer;
+use starry_server::core::{SCREEN_HEIGHT, SCREEN_WIDTH};
 
-use crate::base::{point::Point, rect::Rect};
+use crate::base::{rect::Rect, vector2::Vector2};
 
 pub mod image;
 pub mod label;
 
-/// 组件的纵向排列方式
-#[derive(PartialEq, Copy, Clone)]
-pub enum VerticalPlacement {
-    /// 向上对齐
-    Top,
-    /// 居中对齐
-    Center,
-    /// 向下对齐
-    Bottom,
-    /// 绝对位置
-    Absolute,
-    /// 拉伸
-    Stretch,
+pub fn widget_add_child(parent: Arc<dyn Widget>, child: Arc<dyn Widget>) {
+    parent.children().borrow_mut().push(child.clone());
+    (*child.parent().borrow_mut()) = Some(parent.clone());
+    parent.arrange_all();
+}
+
+/// # 函数功能
+/// 工具类 根据pivot和offset来进行矩形位置的对齐
+///
+/// ## 参数
+/// - origin_rect: 待对齐的矩形
+/// - relative_rect: 作为对齐参考的矩形
+/// - pivot: 对齐方式
+/// - pivot_offset: 偏移量
+///
+/// ## 返回值
+/// 对齐后的矩形
+pub fn align_rect(
+    origin_rect: Rect,
+    relative_rect: Rect,
+    pivot: PivotType,
+    pivot_offset: Vector2,
+) -> Rect {
+    let relative_pos = match pivot {
+        PivotType::None => Vector2::new(0, 0),
+        PivotType::Bottom => relative_rect.bottom_pos(),
+        PivotType::BottomLeft => relative_rect.bottom_left_pos(),
+        PivotType::BottomRight => relative_rect.bottom_right_pos(),
+        PivotType::Center => relative_rect.center_pos(),
+        PivotType::Top => relative_rect.top_pos(),
+        PivotType::TopLeft => relative_rect.top_left_pos(),
+        PivotType::TopRight => relative_rect.top_right_pos(),
+        PivotType::Left => relative_rect.left_pos(),
+        PivotType::Right => relative_rect.right_pos(),
+    };
+
+    let mut target_pos = relative_pos + pivot_offset;
+
+    let negative_width = -(origin_rect.width as i32);
+    let negative_height = -(origin_rect.height as i32);
+    let offset_vec = match pivot {
+        PivotType::None => Vector2::new(0, 0),
+        PivotType::Bottom => Vector2::new(negative_width / 2, negative_height),
+        PivotType::BottomLeft => Vector2::new(0, negative_height),
+        PivotType::BottomRight => Vector2::new(negative_width, negative_height),
+        PivotType::Center => Vector2::new(negative_width / 2, negative_height / 2),
+        PivotType::Top => Vector2::new(negative_width / 2, 0),
+        PivotType::TopLeft => Vector2::new(0, 0),
+        PivotType::TopRight => Vector2::new(negative_width, 0),
+        PivotType::Left => Vector2::new(0, negative_height / 2),
+        PivotType::Right => Vector2::new(negative_width, negative_height / 2),
+    };
+
+    target_pos = target_pos + offset_vec;
+    Rect::new(
+        target_pos.x,
+        target_pos.y,
+        origin_rect.width,
+        origin_rect.height,
+    )
 }
 
-/// 组件的横向排列方式
 #[derive(PartialEq, Copy, Clone)]
-pub enum HorizontalPlacement {
-    /// 靠左对齐
+pub enum PivotType {
+    /// 不进行对齐 pivot_offset即为世界坐标
+    None,
+    /// 对齐左上角(默认对齐方式,这是由于矩形位置通过左上角顶点坐标来表示)
+    TopLeft,
+    /// 对齐正上方
+    Top,
+    /// 对齐右上角
+    TopRight,
+    /// 对齐正左方
     Left,
-    /// 居中对齐
+    /// 对齐中心
     Center,
-    /// 靠右对齐
+    /// 对齐正右方
     Right,
-    /// 绝对位置
-    Absolute,
-    /// 拉伸
-    Stretch,
+    /// 对齐左下角
+    BottomLeft,
+    /// 对齐正下方
+    Bottom,
+    /// 对齐右下角
+    BottomRight,
 }
 
 ///  UI组件需要实现的特性
@@ -46,86 +103,89 @@ pub trait Widget: Any {
     /// 返回渲染的矩形区域
     fn rect(&self) -> &Cell<Rect>;
 
-    /// 返回组件相对于父物体的相对位置
-    fn local_position(&self) -> &Cell<Point>;
-
-    /// 返回纵向排列方式
-    fn vertical_placement(&self) -> &Cell<VerticalPlacement>;
+    /// 对齐方式
+    fn pivot(&self) -> &Cell<PivotType>;
 
-    /// 返回横向排列方式
-    fn horizontal_placement(&self) -> &Cell<HorizontalPlacement>;
+    /// 基于基准点的偏移量
+    fn pivot_offset(&self) -> &Cell<Vector2>;
 
     /// 返回组件的名字
     fn name(&self) -> &str;
 
+    /// 返回父物体
+    fn parent(&self) -> &RefCell<Option<Arc<dyn Widget>>>;
+
     /// 返回子组件数组
     fn children(&self) -> &RefCell<Vec<Arc<dyn Widget>>>;
 
-    /// 添加子组件
-    fn add_child(&self, widget: Arc<dyn Widget>) {
-        (*self.children().borrow_mut()).push(widget);
-        self.arrange();
-    }
-
     /// 渲染组件
     fn draw(&self, renderer: &mut dyn Renderer, focused: bool);
 
     /// 更新组件状态
     fn update(&self) {}
 
-    /// 重新排布子对象
-    /// TODO 增加margin字段后相应处理
-    fn arrange(&self) {
-        let parent_rect = self.rect().get();
+    fn set_pivot_type(&self, pivot_type: PivotType) {
+        self.set_pivot_type_base(pivot_type);
+    }
+
+    /// 修改对齐方式的统一处理 方便覆写
+    fn set_pivot_type_base(&self, pivot_type: PivotType) {
+        self.pivot().set(pivot_type);
+        self.arrange_all();
+    }
+
+    fn set_pivot_offset(&self, pivot_offset: Vector2) {
+        self.set_pivot_offset_base(pivot_offset);
+    }
+
+    /// 修改对齐偏移量的统一处理 方便覆写
+    fn set_pivot_offset_base(&self, pivot_offset: Vector2) {
+        self.pivot_offset().set(pivot_offset);
+        self.arrange_all();
+    }
+
+    fn resize(&self, width: u32, height: u32) {
+        self.resize_base(width, height);
+    }
+
+    /// 修改大小时的统一处理 方便覆写
+    fn resize_base(&self, width: u32, height: u32) {
+        let mut rect = self.rect().get();
+        rect.width = width;
+        rect.height = height;
+        self.rect().set(rect);
+        self.arrange_all();
+    }
+
+    /// 重新排布自身和子对象的位置
+    fn arrange_all(&self) {
+        self.arrange_self();
 
         for child in self.children().borrow_mut().iter() {
-            let mut child_rect = child.rect().get();
-            let child_position = child.local_position().get();
-
-            match child.vertical_placement().get() {
-                VerticalPlacement::Absolute => {
-                    child_rect.y = parent_rect.y + child_position.y;
-                }
-                VerticalPlacement::Stretch => {
-                    child_rect.height = parent_rect.height;
-                    child_rect.y = parent_rect.y;
-                }
-                VerticalPlacement::Top => {
-                    child_rect.y = parent_rect.y;
-                }
-                VerticalPlacement::Center => {
-                    child_rect.y = parent_rect.y + parent_rect.height as i32 / 2
-                        - child_rect.height as i32 / 2;
-                }
-                VerticalPlacement::Bottom => {
-                    child_rect.y =
-                        parent_rect.y + parent_rect.height as i32 - child_rect.height as i32;
-                }
-            }
-
-            match child.horizontal_placement().get() {
-                HorizontalPlacement::Absolute => {
-                    child_rect.x = parent_rect.x + child_position.x;
-                }
-                HorizontalPlacement::Stretch => {
-                    child_rect.width = parent_rect.width;
-                    child_rect.x = parent_rect.x;
-                }
-                HorizontalPlacement::Left => {
-                    child_rect.x = parent_rect.x;
-                }
-                HorizontalPlacement::Center => {
-                    child_rect.x =
-                        parent_rect.x + parent_rect.width as i32 / 2 - child_rect.width as i32 / 2;
-                }
-                HorizontalPlacement::Right => {
-                    child_rect.x =
-                        parent_rect.x + parent_rect.width as i32 - child_rect.width as i32;
-                }
-            }
-
-            child.rect().set(child_rect);
-            child.arrange();
+            child.arrange_all();
         }
     }
+
+    fn arrange_self(&self) {
+        self.arrange_self_base();
+    }
+
+    /// 根据父物体和pivot值来调整自身位置 统一处理 方便覆写
+    fn arrange_self_base(&self) {
+        let relative_rect: Rect = if self.parent().borrow().is_some() {
+            self.parent().borrow().as_ref().unwrap().rect().get()
+        } else {
+            // 没有父物体 则以整个屏幕作为参考
+            Rect::new(0, 0, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32)
+        };
+
+        let target_rect = align_rect(
+            self.rect().get(),
+            relative_rect,
+            self.pivot().get(),
+            self.pivot_offset().get(),
+        );
+
+        self.rect().set(target_rect);
+    }
 }