浏览代码

完善Toolkit库 (#16)

R0ronoa 11 月之前
父节点
当前提交
731cae0674

+ 6 - 6
starry_applications/src/asset_manager/code/asset_item.rs → starry_applications/src/asset_manager/code/asset_item_grid.rs

@@ -21,8 +21,8 @@ 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 struct AssetItem {
-    self_ref: RefCell<Weak<AssetItem>>,
+pub struct AssetItemGrid {
+    self_ref: RefCell<Weak<AssetItemGrid>>,
     pub rect: Cell<Rect>,
     pivot: Cell<PivotType>,
     pivot_offset: Cell<Vector2>,
@@ -35,12 +35,12 @@ pub struct AssetItem {
     pub is_dir: Cell<bool>,
 }
 
-impl AssetItem {
+impl AssetItemGrid {
     pub const ITEM_WIDTH: u32 = 144;
     pub const ITEM_HEIGHT: u32 = 144;
 
     pub fn new(file_name: &str, is_dir: bool) -> Arc<Self> {
-        let item = Arc::new(AssetItem {
+        let item = Arc::new(AssetItemGrid {
             self_ref: RefCell::new(Weak::default()),
             rect: Cell::new(Rect::new(0, 0, Self::ITEM_WIDTH, Self::ITEM_HEIGHT)),
             pivot: Cell::new(PivotType::TopLeft),
@@ -84,7 +84,7 @@ impl AssetItem {
     }
 }
 
-impl Widget for AssetItem {
+impl Widget for AssetItemGrid {
     fn self_ref(&self) -> Arc<dyn Widget> {
         self.self_ref.borrow().upgrade().unwrap()
     }
@@ -94,7 +94,7 @@ impl Widget for AssetItem {
     }
 
     fn name(&self) -> &str {
-        "AssetItem"
+        "AssetItem_Grid"
     }
 
     fn rect(&self) -> &Cell<Rect> {

+ 128 - 0
starry_applications/src/asset_manager/code/asset_item_list.rs

@@ -0,0 +1,128 @@
+use std::{
+    any::Any,
+    cell::{Cell, RefCell},
+    str::FromStr,
+    sync::{Arc, Weak},
+};
+
+use starry_client::base::{color::Color, renderer::Renderer};
+use starry_toolkit::{
+    base::{panel::Panel, rect::Rect, vector2::Vector2},
+    traits::text::Text,
+    widgets::{
+        image::Image,
+        label::{Label, LabelOverflowType},
+        PivotType, Widget,
+    },
+};
+
+pub struct AssetItemList {
+    self_ref: RefCell<Weak<AssetItemList>>,
+    pub rect: Cell<Rect>,
+    pivot: Cell<PivotType>,
+    pivot_offset: Cell<Vector2>,
+    parent: RefCell<Option<Arc<dyn Widget>>>,
+    children: RefCell<Vec<Arc<dyn Widget>>>,
+    panel: RefCell<Option<Arc<Panel>>>,
+    /// 缓存值
+    cache_focused: Cell<bool>,
+    pub file_path: RefCell<String>,
+}
+
+impl AssetItemList {
+    pub const ITEM_WIDTH: u32 = 1024;
+    pub const ITEM_HEIGHT: u32 = 32;
+
+    pub fn new(file_name: &str, is_dir: bool) -> Arc<Self> {
+        let item = Arc::new(AssetItemList {
+            self_ref: RefCell::new(Weak::default()),
+            rect: Cell::new(Rect::new(0, 0, Self::ITEM_WIDTH, Self::ITEM_HEIGHT)),
+            pivot: Cell::new(PivotType::TopLeft),
+            pivot_offset: Cell::new(Vector2::new(0, 0)),
+            children: RefCell::new(Vec::new()),
+            parent: RefCell::new(None),
+            panel: RefCell::new(None),
+            cache_focused: Cell::new(false),
+            file_path: RefCell::new(String::from_str(file_name).unwrap()),
+        });
+
+        (*item.self_ref.borrow_mut()) = Arc::downgrade(&item);
+
+        // 背景Image
+        let bg = Image::new_from_color(
+            Self::ITEM_WIDTH,
+            Self::ITEM_HEIGHT,
+            Color::rgba(0, 255, 255, 64),
+        );
+        bg.set_pivot_type(PivotType::Center);
+        item.add_child(bg);
+
+        // 文件名Label
+        let name = Label::new();
+        name.set_adapt_type(LabelOverflowType::Omit);
+        name.resize(256, 16);
+        name.set_text(file_name);
+        name.set_pivot_type(PivotType::Left);
+        name.set_pivot_offset(Vector2::new(20, 0));
+        name.set_text_pivot_type(PivotType::Left);
+        item.add_child(name);
+
+        // 文件类型Label
+        let file_type = Label::new();
+        file_type.set_adapt_type(LabelOverflowType::ShinkToFit);
+        let type_name = if is_dir { "direction" } else { "file" };
+        file_type.set_text(type_name);
+        file_type.set_pivot_type(PivotType::Center);
+        item.add_child(file_type);
+
+        return item;
+    }
+}
+
+impl Widget for AssetItemList {
+    fn self_ref(&self) -> Arc<dyn Widget> {
+        self.self_ref.borrow().upgrade().unwrap()
+    }
+
+    fn as_any_ref(&self) -> &dyn Any {
+        self
+    }
+
+    fn name(&self) -> &str {
+        "AssetItem_List"
+    }
+
+    fn rect(&self) -> &Cell<Rect> {
+        &self.rect
+    }
+
+    fn pivot(&self) -> &Cell<PivotType> {
+        &self.pivot
+    }
+
+    fn pivot_offset(&self) -> &Cell<Vector2> {
+        &self.pivot_offset
+    }
+
+    fn parent(&self) -> &RefCell<Option<Arc<dyn Widget>>> {
+        &self.parent
+    }
+
+    fn children(&self) -> &RefCell<Vec<Arc<dyn Widget>>> {
+        &self.children
+    }
+
+    fn panel(&self) -> &RefCell<Option<Arc<Panel>>> {
+        &self.panel
+    }
+
+    fn draw(&self, renderer: &mut dyn Renderer, focused: bool) {
+        if focused != self.cache_focused.get() {
+            self.cache_focused.set(focused);
+        }
+
+        for child in self.children.borrow().iter() {
+            child.draw(renderer, focused);
+        }
+    }
+}

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

@@ -1,8 +1,10 @@
-use self::asset_item::AssetItem;
+use self::asset_item_grid::AssetItemGrid;
+use self::asset_item_list::AssetItemList;
 use crate::starry_toolkit::traits::focus::Focus;
 use starry_client::base::color::Color;
 use starry_server::base::image::Image as ImageResource;
 use starry_server::core::{SCREEN_HEIGHT, SCREEN_WIDTH};
+use starry_toolkit::layout::list::{List, ListArrangeType};
 use starry_toolkit::{
     base::{panel::Panel, rect::Rect},
     layout::grid::{Grid, GridArrangeType},
@@ -10,7 +12,8 @@ use starry_toolkit::{
     widgets::image::Image,
 };
 use std::{collections::BTreeMap, fs, sync::Arc};
-pub mod asset_item;
+pub mod asset_item_grid;
+pub mod asset_item_list;
 
 const DESKTOP_BG_PATH: &[u8] = include_bytes!("../resource/desktop_bg.png");
 const LOADING_IMG_PATH: &[u8] = include_bytes!("../resource/loading.png");
@@ -18,7 +21,8 @@ const LOADING_IMG_PATH: &[u8] = include_bytes!("../resource/loading.png");
 pub struct AssetManager {
     cur_path: String,
     asset_grid: Arc<Grid>,
-    items: BTreeMap<(usize, usize), Arc<AssetItem>>,
+    asset_list: Arc<List>,
+    items: BTreeMap<(usize, usize), Arc<AssetItemGrid>>,
     panel: Arc<Panel>,
     // 原则上一个应用程序对应一个Panel和Window
     // 这里额外创建一个Panel用于Loading图标优先显示
@@ -32,6 +36,7 @@ impl AssetManager {
         AssetManager {
             cur_path: String::from("/"),
             asset_grid: Grid::new(),
+            asset_list: List::new(),
             items: BTreeMap::new(),
             panel: Panel::new(
                 Rect::new(0, 0, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32),
@@ -47,7 +52,7 @@ impl AssetManager {
         }
     }
 
-    pub fn init(&mut self) {
+    pub fn init_grid(&mut self) {
         self.init_loading_panel();
 
         let grid = self.asset_grid.clone();
@@ -82,7 +87,7 @@ impl AssetManager {
                         asset_manager.cur_path.push_str(&item.file_path.borrow());
                         asset_manager.cur_path.push_str(&"/");
                     }
-                    asset_manager.refresh();
+                    asset_manager.refresh_grid();
                 }
 
                 return;
@@ -154,6 +159,22 @@ impl AssetManager {
         ));
 
         self.panel.add_child(&(self.asset_grid));
+        // self.panel.set_renderer_mode(PanelRendererMode::WithWireframe);
+    }
+
+    pub fn init_list(&mut self) {
+        self.init_loading_panel();
+
+        let list = self.asset_list.clone();
+        list.set_arrange_type(ListArrangeType::Vertical);
+        list.set_space(3);
+
+        self.panel.add_child(&Image::new_from_image(
+            ImageResource::from_path(DESKTOP_BG_PATH).unwrap(),
+        ));
+
+        self.panel.add_child(&(self.asset_list));
+        // self.panel.set_renderer_mode(PanelRendererMode::WithWireframe);
     }
 
     fn init_loading_panel(&mut self) {
@@ -162,12 +183,12 @@ impl AssetManager {
         ));
     }
 
-    pub fn refresh(&mut self) {
+    pub fn refresh_grid(&mut self) {
         self.items.clear();
         self.asset_grid.clear();
 
         // 父目录
-        let parent_asset_item = AssetItem::new("..", true);
+        let parent_asset_item = AssetItemGrid::new("..", true);
         let (row, col) = self.asset_grid.add_element(&parent_asset_item);
         self.items.insert((row, col), parent_asset_item.clone());
 
@@ -181,7 +202,7 @@ impl AssetManager {
                         false
                     };
 
-                    let asset_item = AssetItem::new(item.file_name().to_str().unwrap(), is_dir);
+                    let asset_item = AssetItemGrid::new(item.file_name().to_str().unwrap(), is_dir);
                     let (row, col) = self.asset_grid.add_element(&asset_item);
                     self.items.insert((row, col), asset_item.clone());
                 }
@@ -207,6 +228,42 @@ impl AssetManager {
         self.panel.draw();
     }
 
+    pub fn refresh_list(&mut self) {
+        self.items.clear();
+
+        // 父目录
+        // let parent_asset_item = AssetItem_List::new("..", true);
+        // let _index= self.asset_list.add_element(&parent_asset_item);
+
+        // 读取目录中的文件列表
+        if let Ok(entries) = fs::read_dir(&self.cur_path) {
+            for entry in entries {
+                if let Ok(item) = entry {
+                    let is_dir = if let Ok(metadata) = item.metadata() {
+                        metadata.is_dir()
+                    } else {
+                        false
+                    };
+
+                    let asset_item = AssetItemList::new(item.file_name().to_str().unwrap(), is_dir);
+                    let _index = self.asset_list.add_element(&asset_item);
+                }
+            }
+        } else {
+            println!(
+                "[Error] AssetManager failed to read dir {:?}",
+                self.cur_path
+            );
+        }
+
+        if self.init_show == true {
+            self.init_show = false
+        } else {
+            self.loading_panel.draw();
+        }
+        self.panel.draw();
+    }
+
     pub fn exec(&mut self) {
         self.panel.exec();
     }

+ 4 - 2
starry_applications/src/main.rs

@@ -6,8 +6,10 @@ fn main() -> io::Result<()> {
     set_tty()?;
 
     let mut asset_manager = AssetManager::new();
-    asset_manager.init();
-    asset_manager.refresh();
+    asset_manager.init_grid();
+    asset_manager.refresh_grid();
+    // asset_manager.init_list();
+    // asset_manager.refresh_list();
     asset_manager.exec();
 
     Ok(())

+ 57 - 4
starry_client/src/base/event.rs

@@ -6,18 +6,23 @@ pub enum EventOption {
     MouseRelative(MouseRelativeEvent),
     /// 鼠标按键事件
     Button(ButtonEvent),
+    /// 窗口位置移动事件
+    WindowMove(WindowMoveEvent),
+    /// 窗口大小改变事件
+    WindowResize(WindowResizeEvent),
+    /// 未知事件
+    Unknown(Event),
     /// 空事件
     None,
-    /// 未知事件
-    Unknown,
 }
 
-/// TODO: 整理
 pub const EVENT_NONE: i64 = 0;
 pub const EVENT_KEY: i64 = 1;
 pub const EVENT_MOUSE_RELATIVE: i64 = 2;
 pub const EVENT_BUTTON: i64 = 3;
 pub const EVENT_MOUSE_UPDATE: i64 = 4;
+pub const EVENT_WINDOW_MOVE: i64 = 5;
+pub const EVENT_WINDOW_RESIZE: i64 = 6;
 
 /// 通用事件
 #[derive(Copy, Clone, Debug)]
@@ -44,7 +49,7 @@ impl Event {
                 EventOption::MouseRelative(MouseRelativeEvent::from_event(self))
             }
             EVENT_BUTTON => EventOption::Button(ButtonEvent::from_event(self)),
-            _ => EventOption::Unknown,
+            _ => EventOption::Unknown(self),
         }
     }
 }
@@ -174,3 +179,51 @@ impl MouseUpdateEvent {
         }
     }
 }
+
+/// 窗口位置移动事件
+#[derive(Copy, Clone, Debug)]
+pub struct WindowMoveEvent {
+    pub x: i32,
+    pub y: i32,
+}
+
+impl WindowMoveEvent {
+    pub fn to_event(&self) -> Event {
+        Event {
+            code: EVENT_WINDOW_MOVE,
+            a: self.x as i64,
+            b: self.y as i64,
+        }
+    }
+
+    pub fn from_event(event: Event) -> WindowMoveEvent {
+        WindowMoveEvent {
+            x: event.a as i32,
+            y: event.b as i32,
+        }
+    }
+}
+
+/// 窗口改变大小事件
+#[derive(Copy, Clone, Debug)]
+pub struct WindowResizeEvent {
+    pub width: u32,
+    pub height: u32,
+}
+
+impl WindowResizeEvent {
+    pub fn to_event(&self) -> Event {
+        Event {
+            code: EVENT_WINDOW_RESIZE,
+            a: self.width as i64,
+            b: self.height as i64,
+        }
+    }
+
+    pub fn from_event(event: Event) -> WindowResizeEvent {
+        WindowResizeEvent {
+            width: event.a as u32,
+            height: event.b as u32,
+        }
+    }
+}

+ 146 - 148
starry_client/src/base/renderer.rs

@@ -74,13 +74,158 @@ pub trait Renderer {
         }
     }
 
+    /// # 函数功能
+    /// 在指定位置绘制字符
+    ///
+    /// ## 参数
+    /// - x: x坐标(局部坐标)
+    /// - y: y坐标(局部坐标)
+    /// - c: 待绘制的字符
+    /// - color: 字符颜色
+    fn char(&mut self, x: i32, y: i32, c: char, color: Color) {
+        let mut offset = (c as usize) * 16;
+        for row in 0..16 {
+            let row_data = if offset < FONT_ASSET.len() {
+                FONT_ASSET[offset]
+            } else {
+                0
+            };
+
+            for col in 0..8 {
+                let pixel = (row_data >> (7 - col)) & 1;
+                if pixel > 0 {
+                    self.pixel(x + col, y + row, color);
+                }
+            }
+            offset += 1;
+        }
+    }
+
+    /// # 函数功能
+    /// 在指定位置绘制一幅图像至帧缓冲区
+    ///
+    /// ## 参数
+    /// - start_x: 起始x坐标(局部坐标)
+    /// - start_y: 起始y坐标(局部坐标)
+    /// - w: 图像宽度
+    /// - h: 图像高度
+    /// - data: 图像数据
+    fn image(&mut self, start_x: i32, start_y: i32, w: u32, h: u32, data: &[Color]) {
+        match self.mode().get() {
+            RenderMode::Blend => self.image_fast(start_x, start_y, w, h, data),
+            RenderMode::Overwrite => self.image_opaque(start_x, start_y, w, h, data),
+        }
+    }
+
+    /// # 函数功能
+    /// 从指定行开始绘制一幅图像至帧缓冲区
+    ///
+    /// ## 参数
+    /// - start: 起始行数
+    /// - image_data: 图像帧缓冲数据
+    fn image_over(&mut self, start: i32, image_data: &[Color]) {
+        let start = start as usize * self.width() as usize;
+        let window_data = self.data_mut();
+        let stop = cmp::min(start + image_data.len(), window_data.len());
+        let end = cmp::min(image_data.len(), window_data.len() - start);
+
+        window_data[start..stop].copy_from_slice(&image_data[..end]);
+    }
+
+    ///Display an image using non transparent method
+    /// TODO 注释补充
+    #[inline(always)]
+    fn image_opaque(&mut self, start_x: i32, start_y: i32, w: u32, h: u32, image_data: &[Color]) {
+        let w = w as usize;
+        let mut h = h as usize;
+        let width = self.width() as usize;
+        let height = self.height() as usize;
+        let start_x = start_x as usize;
+        let start_y = start_y as usize;
+
+        //check boundaries
+        if start_x >= width || start_y >= height {
+            return;
+        }
+        if h + start_y > height {
+            h = height - start_y;
+        }
+        let window_data = self.data_mut();
+        let offset = start_y * width + start_x;
+        //copy image slices to window line by line
+        for l in 0..h {
+            let start = offset + l * width;
+            let mut stop = start + w;
+            let begin = l * w;
+            let mut end = begin + w;
+            //check boundaries
+            if start_x + w > width {
+                stop = (start_y + l + 1) * width - 1;
+                end = begin + stop - start;
+            }
+            window_data[start..stop].copy_from_slice(&image_data[begin..end]);
+        }
+    }
+
+    /// Speed improved, image can be outside of window boundary
+    /// TODO 注释补充
+    #[inline(always)]
+    fn image_fast(&mut self, start_x: i32, start_y: i32, w: u32, h: u32, image_data: &[Color]) {
+        let w = w as usize;
+        let h = h as usize;
+        let width = self.width() as usize;
+        let start_x = start_x as usize;
+        let start_y = start_y as usize;
+
+        //simply return if image is outside of window
+        if start_x >= width || start_y >= self.height() as usize {
+            return;
+        }
+        let window_data = self.data_mut();
+        let offset = start_y * width + start_x;
+
+        //copy image slices to window line by line
+        for l in 0..h {
+            let start = offset + l * width;
+            let mut stop = start + w;
+            let begin = l * w;
+            let end = begin + w;
+
+            //check boundaries
+            if start_x + w > width {
+                stop = (start_y + l + 1) * width;
+            }
+            let mut k = 0;
+            for i in begin..end {
+                if i < image_data.len() {
+                    let new = image_data[i].data;
+                    let alpha = (new >> 24) & 0xFF;
+                    if alpha > 0 && (start + k) < window_data.len() && (start + k) < stop {
+                        let old = &mut window_data[start + k].data;
+                        if alpha >= 255 {
+                            *old = new;
+                        } else {
+                            let n_alpha = 255 - alpha;
+                            let rb = ((n_alpha * (*old & 0x00FF00FF))
+                                + (alpha * (new & 0x00FF00FF)))
+                                >> 8;
+                            let ag = (n_alpha * ((*old & 0xFF00FF00) >> 8))
+                                + (alpha * (0x01000000 | ((new & 0x0000FF00) >> 8)));
+
+                            *old = (rb & 0x00FF00FF) | (ag & 0xFF00FF00);
+                        }
+                    }
+                    k += 1;
+                }
+            }
+        }
+    }
     /// TODO 注释补充
     fn arc(&mut self, x0: i32, y0: i32, radius: i32, parts: u8, color: Color) {
         let mut x = radius.abs();
         let mut y = 0;
         let mut err = 0;
 
-        // https://github.com/rust-lang/rust-clippy/issues/5354
         while x >= y {
             if radius < 0 {
                 if parts & 1 << 0 != 0 {
@@ -384,151 +529,4 @@ pub trait Renderer {
         }
         return self.data()[p];
     }
-
-    /// # 函数功能
-    /// 在指定位置绘制字符
-    ///
-    /// ## 参数
-    /// - x: x坐标(局部坐标)
-    /// - y: y坐标(局部坐标)
-    /// - c: 待绘制的字符
-    /// - color: 字符颜色
-    fn char(&mut self, x: i32, y: i32, c: char, color: Color) {
-        let mut offset = (c as usize) * 16;
-        for row in 0..16 {
-            let row_data = if offset < FONT_ASSET.len() {
-                FONT_ASSET[offset]
-            } else {
-                0
-            };
-
-            for col in 0..8 {
-                let pixel = (row_data >> (7 - col)) & 1;
-                if pixel > 0 {
-                    self.pixel(x + col, y + row, color);
-                }
-            }
-            offset += 1;
-        }
-    }
-
-    /// # 函数功能
-    /// 在指定位置绘制一幅图像至帧缓冲区
-    ///
-    /// ## 参数
-    /// - start_x: 起始x坐标(局部坐标)
-    /// - start_y: 起始y坐标(局部坐标)
-    /// - w: 图像宽度
-    /// - h: 图像高度
-    /// - data: 图像数据
-    fn image(&mut self, start_x: i32, start_y: i32, w: u32, h: u32, data: &[Color]) {
-        match self.mode().get() {
-            RenderMode::Blend => self.image_fast(start_x, start_y, w, h, data),
-            RenderMode::Overwrite => self.image_opaque(start_x, start_y, w, h, data),
-        }
-    }
-
-    /// # 函数功能
-    /// 从指定行开始绘制一幅图像至帧缓冲区
-    ///
-    /// ## 参数
-    /// - start: 起始行数
-    /// - image_data: 图像帧缓冲数据
-    fn image_over(&mut self, start: i32, image_data: &[Color]) {
-        let start = start as usize * self.width() as usize;
-        let window_data = self.data_mut();
-        let stop = cmp::min(start + image_data.len(), window_data.len());
-        let end = cmp::min(image_data.len(), window_data.len() - start);
-
-        window_data[start..stop].copy_from_slice(&image_data[..end]);
-    }
-
-    ///Display an image using non transparent method
-    /// TODO 注释补充
-    #[inline(always)]
-    fn image_opaque(&mut self, start_x: i32, start_y: i32, w: u32, h: u32, image_data: &[Color]) {
-        let w = w as usize;
-        let mut h = h as usize;
-        let width = self.width() as usize;
-        let height = self.height() as usize;
-        let start_x = start_x as usize;
-        let start_y = start_y as usize;
-
-        //check boundaries
-        if start_x >= width || start_y >= height {
-            return;
-        }
-        if h + start_y > height {
-            h = height - start_y;
-        }
-        let window_data = self.data_mut();
-        let offset = start_y * width + start_x;
-        //copy image slices to window line by line
-        for l in 0..h {
-            let start = offset + l * width;
-            let mut stop = start + w;
-            let begin = l * w;
-            let mut end = begin + w;
-            //check boundaries
-            if start_x + w > width {
-                stop = (start_y + l + 1) * width - 1;
-                end = begin + stop - start;
-            }
-            window_data[start..stop].copy_from_slice(&image_data[begin..end]);
-        }
-    }
-
-    /// Speed improved, image can be outside of window boundary
-    /// TODO 注释补充
-    #[inline(always)]
-    fn image_fast(&mut self, start_x: i32, start_y: i32, w: u32, h: u32, image_data: &[Color]) {
-        let w = w as usize;
-        let h = h as usize;
-        let width = self.width() as usize;
-        let start_x = start_x as usize;
-        let start_y = start_y as usize;
-
-        //simply return if image is outside of window
-        if start_x >= width || start_y >= self.height() as usize {
-            return;
-        }
-        let window_data = self.data_mut();
-        let offset = start_y * width + start_x;
-
-        //copy image slices to window line by line
-        for l in 0..h {
-            let start = offset + l * width;
-            let mut stop = start + w;
-            let begin = l * w;
-            let end = begin + w;
-
-            //check boundaries
-            if start_x + w > width {
-                stop = (start_y + l + 1) * width;
-            }
-            let mut k = 0;
-            for i in begin..end {
-                if i < image_data.len() {
-                    let new = image_data[i].data;
-                    let alpha = (new >> 24) & 0xFF;
-                    if alpha > 0 && (start + k) < window_data.len() && (start + k) < stop {
-                        let old = &mut window_data[start + k].data;
-                        if alpha >= 255 {
-                            *old = new;
-                        } else {
-                            let n_alpha = 255 - alpha;
-                            let rb = ((n_alpha * (*old & 0x00FF00FF))
-                                + (alpha * (new & 0x00FF00FF)))
-                                >> 8;
-                            let ag = (n_alpha * ((*old & 0xFF00FF00) >> 8))
-                                + (alpha * (0x01000000 | ((new & 0x0000FF00) >> 8)));
-
-                            *old = (rb & 0x00FF00FF) | (ag & 0xFF00FF00);
-                        }
-                    }
-                    k += 1;
-                }
-            }
-        }
-    }
 }

+ 3 - 5
starry_client/src/window.rs

@@ -33,14 +33,12 @@ pub struct Window {
     h: u32,
     /// 窗口的标题
     title: String,
-    /// TODO
-    // window_async: bool,
     /// 窗口是否大小可变
     resizable: bool,
     /// 窗口的渲染模式
     mode: Cell<RenderMode>,
-    // TODO
-    // file_opt: Option<File>,
+    // 命名管道文件
+    file_opt: Option<File>,
     // TODO: 改定长数组
     // data_opt: Option<& 'static mut [Color]>,
     /// 窗口的渲染数据
@@ -105,7 +103,7 @@ impl Window {
             // window_async: false,
             resizable: false,
             mode: Cell::new(RenderMode::Blend),
-            // file_opt: None,
+            file_opt: None,
             data_opt: Some(vec![color; (w * h) as usize].into_boxed_slice()),
             fb_file: File::open(FB_FILE_PATH).expect("[Error] Window failed to open fb file"),
         }

+ 14 - 11
starry_server/src/base/window.rs

@@ -1,3 +1,5 @@
+use std::fs::File;
+
 use starry_client::base::event::Event;
 
 use crate::core::SCREEN_HEIGHT;
@@ -26,25 +28,22 @@ pub struct Window {
     pub scale: i32,
     /// 窗口标题
     pub title: String,
-    // pub asynchronous: bool,
-    // pub barderless: bool,
-    // pub resizable: bool,
+    /// 是否无边界
+    pub barderless: bool,
+    /// 是否大小可变
+    pub resizable: bool,
     /// 是否透明
     pub transparent: bool,
-    // pub unclosable: bool,
+    /// 是否不可关闭
+    pub unclosable: bool,
     /// 排序模式
     pub zorder: WindowZOrderMode,
-
     /// 窗体图像
     pub image: Image,
-    /// 窗口标题图像(聚焦)
-    // pub title_image: Image,
-    /// 窗口标题图像(非聚焦)
-    // pub title_image_unfocused: Image,
-
     /// 事件数组
     pub events: Vec<Event>,
-    // TODO 增加所需字段
+    // 命名管道文件
+    pub file_opt: Option<File>,
 }
 
 impl Window {
@@ -54,11 +53,15 @@ impl Window {
             y: y,
             scale: scale,
             title: String::new(),
+            barderless: false,
             transparent: false,
+            resizable: true,
+            unclosable: false,
             zorder: WindowZOrderMode::Normal,
             image: Image::from_path(image_path)
                 .unwrap_or(Image::new(SCREEN_HEIGHT as i32, SCREEN_HEIGHT as i32)),
             events: Vec::new(),
+            file_opt: None,
         }
     }
 

+ 1 - 1
starry_server/src/core/compositor.rs

@@ -23,7 +23,7 @@ pub fn compositor() -> Option<Arc<Compositor>> {
 #[allow(dead_code)]
 /// 合成渲染器
 pub struct Compositor {
-    /// 待重绘的窗口
+    /// 待重绘的矩形区域
     redraws: RefCell<Vec<Rect>>,
     /// 帧缓冲文件
     fb_file: RefCell<File>,

+ 29 - 0
starry_server/src/core/input/inputs.rs

@@ -4,6 +4,7 @@ use starry_client::base::event::{ButtonEvent, Event, MouseRelativeEvent};
 use std::{fs::File, io::Read};
 
 const MOUSE_DEVICE_PATH: &str = "/dev/char/psmouse";
+const KBD_DEVICE_PATH: &str = "/dev/char/tty0";
 
 bitflags! {
     /// 鼠标状态掩码
@@ -126,3 +127,31 @@ impl InputHandler for MouseInputHandler {
         return events;
     }
 }
+
+// TODO
+pub struct KeyboardInputHandler {
+    /// 读取的文件
+    file: File,
+}
+
+impl KeyboardInputHandler {
+    pub fn new() -> Box<KeyboardInputHandler> {
+        let file = File::open(KBD_DEVICE_PATH).expect("Fail to open mouse device");
+        // println!("[Init] Keyboard_Input_Handler created successfully!");
+        Box::new(KeyboardInputHandler { file: file })
+    }
+}
+
+impl InputHandler for KeyboardInputHandler {
+    fn get_listening_file(&mut self) -> &File {
+        &self.file
+    }
+
+    fn set_listening_file(&mut self, file: File) {
+        self.file = file;
+    }
+
+    fn handle(&mut self, _packet: u8) -> Vec<Event> {
+        Vec::new()
+    }
+}

+ 15 - 13
starry_server/src/core/input/mod.rs

@@ -2,7 +2,7 @@ use std::{cell::RefCell, fs::File, io::Read, sync::Arc};
 
 use starry_client::base::event::Event;
 
-use self::inputs::MouseInputHandler;
+use self::inputs::{KeyboardInputHandler, MouseInputHandler};
 
 use super::window_manager::window_manager;
 
@@ -25,9 +25,8 @@ impl InputManager {
     /// 创建输入管理器
     pub fn new() {
         let mut input_handlers = Vec::new();
-        // TODO: 通过设备检测添加
         input_handlers.push(MouseInputHandler::new() as Box<dyn InputHandler>);
-        // TODO: 处理键盘输入
+        input_handlers.push(KeyboardInputHandler::new() as Box<dyn InputHandler>);
         let input_manager = InputManager {
             handlers: RefCell::new(input_handlers),
         };
@@ -39,7 +38,7 @@ impl InputManager {
         // println!("[Init] Input_Manager created successfully!");
     }
 
-    /// 轮询所有输入设备
+    /// 驱动所有输入处理器
     pub fn polling_all(&self) {
         // println!("[Info] Input_Manager polling all");
         for handle in self.handlers.borrow_mut().iter_mut() {
@@ -63,15 +62,18 @@ pub trait InputHandler {
     /// 轮询文件
     fn polling(&mut self) {
         let mut buf: [u8; 1024] = [0; 1024];
-        // TODO: 错误信息提示相应文件路径
-        let count = self
-            .get_listening_file()
-            .read(&mut buf)
-            .expect("[Error] Fail to polling file");
-        // println!("[Info] Input_Handler polling read {:?} bytes", count);
-        for i in 0..count {
-            let events = self.handle(buf[i]);
-            window_manager().unwrap().send_events(events);
+        let mut file = self.get_listening_file();
+        let result = file.read(&mut buf);
+
+        if result.is_err() {
+            println!("[Error] Filed to polling file {:?}", file);
+        } else {
+            let count = result.ok().unwrap();
+            // println!("[Info] Input_Handler polling read {:?} bytes", count);
+            for i in 0..count {
+                let events = self.handle(buf[i]);
+                window_manager().unwrap().send_events(events);
+            }
         }
     }
 }

+ 5 - 4
starry_server/src/core/mod.rs

@@ -85,10 +85,11 @@ impl StarryServer {
 
         // println!("[Init] Starry_Server start main loop!");
         loop {
-            input_manager().unwrap().polling_all();
-            window_manager().unwrap().handle_all_events();
-            compositor().unwrap().redraw_all();
-            // std::thread::sleep(std::time::Duration::from_millis(1));
+            input_manager().unwrap().polling_all(); // 轮询所有设备文件
+            window_manager().unwrap().polling_all_windows(); // 轮询所有窗口通信管道
+            window_manager().unwrap().handle_all_events(); // 处理所有事件
+            compositor().unwrap().redraw_all(); // 重绘所有更新区域
+            std::thread::sleep(std::time::Duration::from_millis(1));
         }
     }
 }

+ 29 - 6
starry_server/src/core/window_manager.rs

@@ -5,7 +5,10 @@ use std::{
     sync::Arc,
 };
 
-use starry_client::base::event::{Event, EventOption, MouseRelativeEvent, MouseUpdateEvent};
+use starry_client::base::event::{
+    ButtonEvent, Event, EventOption, KeyEvent, MouseRelativeEvent, MouseUpdateEvent,
+    WindowMoveEvent, WindowResizeEvent,
+};
 
 use crate::{
     base::{
@@ -173,22 +176,30 @@ impl WindowManager {
         }
     }
 
+    pub fn polling_all_windows(&self) {}
+
     /// # 函数功能
     /// 处理事件
     ///
     /// ## 参数
     /// 事件对象
-    pub fn handle_event(&self, event_union: Event) {
+    fn handle_event(&self, event_union: Event) {
         // println!("[Info] Window_Manager handle event {:?}", event_union.to_option());
         match event_union.to_option() {
             EventOption::MouseRelative(event) => self.handle_mouse_relative_event(event),
-            EventOption::Button(_event) => {}
-            unknown => println!("[Error] Unexpected event: {:?}", unknown),
+            EventOption::Button(event) => self.handle_button_event(event),
+            EventOption::Key(event) => self.handle_key_event(event),
+            EventOption::WindowMove(event) => self.handle_window_move_event(event),
+            EventOption::WindowResize(event) => self.handle_window_resize_event(event),
+            EventOption::Unknown(event) => {
+                println!("[Error] WindowManager handle unkonwn event {:?}", event)
+            }
+            EventOption::None => {}
         }
     }
 
     /// 处理鼠标相对移动事件
-    pub fn handle_mouse_relative_event(&self, event: MouseRelativeEvent) {
+    fn handle_mouse_relative_event(&self, event: MouseRelativeEvent) {
         // TODO: 将事件传递给窗口,同时考虑窗口对鼠标位置的影响
 
         let max_x: i32 = SCREEN_WIDTH as i32;
@@ -209,7 +220,7 @@ impl WindowManager {
     }
 
     /// 处理鼠标移动事件
-    pub fn handle_mouse_update_event(&self, event: MouseUpdateEvent) {
+    fn handle_mouse_update_event(&self, event: MouseUpdateEvent) {
         let /*mut*/ new_cursor = CursorKind::Normal;
 
         // TODO: 判断新的鼠标状态
@@ -218,6 +229,18 @@ impl WindowManager {
         self.update_cursor(event.x, event.y, new_cursor);
     }
 
+    // TODO
+    fn handle_button_event(&self, _event: ButtonEvent) {}
+
+    // TODO
+    fn handle_key_event(&self, _event: KeyEvent) {}
+
+    // TODO
+    fn handle_window_move_event(&self, _event: WindowMoveEvent) {}
+
+    // TODO
+    fn handle_window_resize_event(&self, _event: WindowResizeEvent) {}
+
     /// # 函数功能
     /// 更新鼠标状态
     ///

+ 57 - 4
starry_toolkit/src/base/panel.rs

@@ -22,6 +22,16 @@ const TTY_DEVICE_PATH: &str = "/dev/char/tty0";
 
 const DURATION_TIME: Duration = Duration::from_millis(25);
 
+#[derive(PartialEq, Copy, Clone, Debug)]
+pub enum PanelRendererMode {
+    /// 标准模式
+    Normal,
+    /// 绘制线框
+    WithWireframe,
+    /// 仅线框
+    OnlyWireframe,
+}
+
 /// 面板渲染器
 pub struct PanelRenderer<'a> {
     /// 客户端窗口
@@ -30,7 +40,7 @@ pub struct PanelRenderer<'a> {
 
 impl<'a> PanelRenderer<'a> {
     pub fn new(window: &'a mut Window) -> Self {
-        PanelRenderer { window }
+        PanelRenderer { window: window }
     }
 }
 
@@ -73,7 +83,7 @@ pub struct Panel {
     window: RefCell<Window>,
     /// 面板矩形
     rect: Cell<Rect>,
-    /// 子组件数组
+    /// 管理的控件对象数组
     widgets: RefCell<Vec<Arc<dyn Widget>>>,
     /// 窗口是否打开
     running: Cell<bool>,
@@ -85,6 +95,8 @@ pub struct Panel {
     redraw: Cell<bool>,
     /// tty文件
     tty_file: RefCell<File>,
+    /// 渲染模式
+    renderer_mode: Cell<PanelRendererMode>,
 }
 
 impl Panel {
@@ -108,6 +120,7 @@ impl Panel {
             tty_file: RefCell::new(
                 File::open(TTY_DEVICE_PATH).expect("[Error] Panel failed to open tty file"),
             ),
+            renderer_mode: Cell::new(PanelRendererMode::Normal),
         });
 
         (*panel.self_ref.borrow_mut()) = Arc::downgrade(&panel);
@@ -173,6 +186,11 @@ impl Panel {
         (*window).set_title(title);
     }
 
+    /// 设置是否绘制线框
+    pub fn set_renderer_mode(&self, renderer_mode: PanelRendererMode) {
+        self.renderer_mode.set(renderer_mode);
+    }
+
     /// 关闭窗口
     pub fn close(&self) {
         self.running.set(false);
@@ -207,14 +225,49 @@ impl Panel {
     /// 渲染单个组件
     pub fn draw_widget(&self, renderer: &mut dyn Renderer, widget: &Arc<dyn Widget>) {
         widget.update();
-        widget.draw(renderer, self.is_focused(widget));
+
+        if self.renderer_mode.get() == PanelRendererMode::Normal
+            || self.renderer_mode.get() == PanelRendererMode::WithWireframe
+        {
+            widget.draw(renderer, self.is_focused(widget));
+        }
+
+        if self.renderer_mode.get() == PanelRendererMode::WithWireframe
+            || self.renderer_mode.get() == PanelRendererMode::OnlyWireframe
+        {
+            Self::draw_rect_wireframe(renderer, widget.rect().get(), Color::rgb(0, 0, 0));
+        }
 
         // 渲染子组件
         for child in widget.children().borrow().iter() {
-            self.draw_widget(renderer, child);
+            if self.renderer_mode.get() == PanelRendererMode::Normal
+                || self.renderer_mode.get() == PanelRendererMode::WithWireframe
+            {
+                self.draw_widget(renderer, child);
+            }
+
+            if self.renderer_mode.get() == PanelRendererMode::WithWireframe
+                || self.renderer_mode.get() == PanelRendererMode::OnlyWireframe
+            {
+                Self::draw_rect_wireframe(renderer, child.rect().get(), Color::rgb(0, 0, 0));
+            }
         }
     }
 
+    /// 绘制矩形线框
+    fn draw_rect_wireframe(renderer: &mut dyn Renderer, rect: Rect, color: Color) {
+        renderer.lines(
+            &[
+                [rect.top_left_pos().x, rect.top_left_pos().y],
+                [rect.top_right_pos().x, rect.top_right_pos().y],
+                [rect.bottom_right_pos().x, rect.bottom_right_pos().y],
+                [rect.bottom_left_pos().x, rect.bottom_left_pos().y],
+                [rect.top_left_pos().x, rect.top_left_pos().y],
+            ],
+            color,
+        );
+    }
+
     pub fn tick(&self) {
         // TODO 通过服务器,先从Window对象接收事件,再进行处理
         self.handle_events();

+ 240 - 0
starry_toolkit/src/layout/list.rs

@@ -0,0 +1,240 @@
+use std::{
+    any::Any,
+    cell::{Cell, RefCell},
+    collections::BTreeMap,
+    sync::{Arc, Weak},
+};
+
+use starry_client::base::renderer::Renderer;
+
+use crate::{
+    base::{panel::Panel, rect::Rect, vector2::Vector2},
+    traits::focus::Focus,
+    widgets::{PivotType, Widget},
+};
+
+#[derive(PartialEq, Copy, Clone)]
+pub enum ListArrangeType {
+    /// 横向排列
+    Horizontal,
+    /// 纵向排列
+    Vertical,
+}
+
+#[derive(PartialEq, Copy, Clone)]
+pub enum ListElementPivotType {
+    LeftOrTop,
+    Center,
+    RightOrBottom,
+}
+
+pub struct List {
+    self_ref: RefCell<Weak<List>>,
+    rect: Cell<Rect>,
+    pivot: Cell<PivotType>,
+    pivot_offset: Cell<Vector2>,
+    children: RefCell<Vec<Arc<dyn Widget>>>,
+    parent: RefCell<Option<Arc<dyn Widget>>>,
+    panel: RefCell<Option<Arc<Panel>>>,
+
+    space: Cell<u32>,
+    current_index: Cell<usize>,
+    elements: RefCell<BTreeMap<usize, Arc<dyn Widget>>>,
+    focused_id: Cell<Option<usize>>,
+    focused_widget: RefCell<Option<Arc<dyn Widget>>>,
+
+    arrange_type: Cell<ListArrangeType>,
+    element_pivot_type: Cell<ListElementPivotType>,
+}
+
+impl List {
+    pub fn new() -> Arc<Self> {
+        let list = Arc::new(List {
+            self_ref: RefCell::new(Weak::default()),
+            rect: Cell::new(Rect::default()),
+            pivot: Cell::new(PivotType::TopLeft),
+            pivot_offset: Cell::new(Vector2::new(0, 0)),
+            children: RefCell::new(vec![]),
+            parent: RefCell::new(None),
+            panel: RefCell::new(None),
+            space: Cell::new(0),
+            current_index: Cell::new(0),
+            elements: RefCell::new(BTreeMap::new()),
+            focused_id: Cell::new(None),
+            focused_widget: RefCell::new(None),
+            arrange_type: Cell::new(ListArrangeType::Vertical),
+            element_pivot_type: Cell::new(ListElementPivotType::Center),
+        });
+
+        (*list.self_ref.borrow_mut()) = Arc::downgrade(&list);
+
+        return list;
+    }
+
+    pub fn set_arrange_type(&self, arrange_type: ListArrangeType) -> &Self {
+        self.arrange_type.set(arrange_type);
+        self
+    }
+
+    pub fn set_space(&self, space: u32) -> &Self {
+        self.space.set(space);
+        self
+    }
+
+    pub fn add_element<T: Widget>(&self, element: &Arc<T>) -> usize {
+        self.add_child(element.self_ref());
+
+        self.elements
+            .borrow_mut()
+            .insert(self.current_index.get(), element.clone());
+
+        let res = self.current_index.get();
+        self.current_index.set(res + 1);
+        self.arrange_elements();
+        return res;
+    }
+
+    pub fn arrange_elements(&self) {
+        if self.elements.borrow().is_empty() {
+            return;
+        }
+
+        self.arrange_self();
+
+        // 遍历找到最大的长或宽值
+        let mut max_size: u32 = 0;
+        for (&_index, element) in self.elements.borrow().iter() {
+            match self.arrange_type.get() {
+                ListArrangeType::Horizontal => {
+                    max_size = u32::max(max_size, element.rect().get().height);
+                }
+                ListArrangeType::Vertical => {
+                    max_size = u32::max(max_size, element.rect().get().width);
+                }
+            }
+        }
+
+        let mut x_offset: u32 = 0;
+        let mut y_offset: u32 = 0;
+
+        for (&_index, element) in self.elements.borrow().iter() {
+            let align_vector: Vector2;
+            match self.arrange_type.get() {
+                ListArrangeType::Horizontal => {
+                    align_vector = match self.element_pivot_type.get() {
+                        ListElementPivotType::LeftOrTop => {
+                            Vector2::new(x_offset as i32, y_offset as i32)
+                        }
+                        ListElementPivotType::Center => Vector2::new(
+                            x_offset as i32,
+                            y_offset as i32 + (max_size - element.rect().get().height) as i32 / 2,
+                        ),
+                        ListElementPivotType::RightOrBottom => Vector2::new(
+                            x_offset as i32,
+                            y_offset as i32 + (max_size - element.rect().get().height) as i32,
+                        ),
+                    };
+                }
+                ListArrangeType::Vertical => {
+                    align_vector = match self.element_pivot_type.get() {
+                        ListElementPivotType::LeftOrTop => {
+                            Vector2::new(x_offset as i32, y_offset as i32)
+                        }
+                        ListElementPivotType::Center => Vector2::new(
+                            x_offset as i32 + (max_size - element.rect().get().width) as i32 / 2,
+                            y_offset as i32,
+                        ),
+                        ListElementPivotType::RightOrBottom => Vector2::new(
+                            x_offset as i32 + (max_size - element.rect().get().width) as i32,
+                            y_offset as i32,
+                        ),
+                    }
+                }
+            }
+
+            element.set_pivot_type(PivotType::TopLeft);
+            element.set_pivot_offset(align_vector);
+            element.arrange_all();
+
+            match self.arrange_type.get() {
+                ListArrangeType::Horizontal => {
+                    x_offset += element.rect().get().width + self.space.get();
+                }
+                ListArrangeType::Vertical => {
+                    y_offset += element.rect().get().height + self.space.get();
+                }
+            }
+        }
+    }
+}
+
+impl Widget for List {
+    fn self_ref(&self) -> Arc<dyn Widget> {
+        self.self_ref.borrow().upgrade().unwrap()
+    }
+
+    fn as_any_ref(&self) -> &dyn Any {
+        self
+    }
+
+    fn name(&self) -> &str {
+        "List"
+    }
+
+    fn rect(&self) -> &Cell<Rect> {
+        &self.rect
+    }
+
+    fn pivot(&self) -> &Cell<PivotType> {
+        &self.pivot
+    }
+
+    fn pivot_offset(&self) -> &Cell<Vector2> {
+        &self.pivot_offset
+    }
+
+    fn parent(&self) -> &RefCell<Option<Arc<dyn Widget>>> {
+        &self.parent
+    }
+
+    fn children(&self) -> &RefCell<Vec<Arc<dyn Widget>>> {
+        &self.children
+    }
+
+    fn panel(&self) -> &RefCell<Option<Arc<Panel>>> {
+        &self.panel
+    }
+
+    fn draw(&self, renderer: &mut dyn Renderer, _focused: bool) {
+        for (&_index, widget) in self.elements.borrow().iter() {
+            widget.update();
+            widget.draw(renderer, self.is_focused(widget));
+        }
+    }
+
+    fn handle_event(
+        &self,
+        _event: crate::base::event::Event,
+        _focused: bool,
+        _redraw: &Cell<bool>,
+        _caught: &Cell<bool>,
+    ) -> bool {
+        false
+    }
+}
+
+impl Focus for List {
+    fn focused_widget(&self) -> RefCell<Option<Arc<dyn Widget>>> {
+        self.focused_widget.clone()
+    }
+
+    fn focus(&self, focused_widget: &Arc<dyn Widget>) {
+        // 同时更新focused_id
+        for (&index, widget) in self.elements.borrow().iter() {
+            if Arc::ptr_eq(widget, focused_widget) {
+                self.focused_id.set(Some(index));
+                (*self.focused_widget.borrow_mut()) = Some(focused_widget.clone());
+            }
+        }
+    }
+}

+ 1 - 0
starry_toolkit/src/layout/mod.rs

@@ -1 +1,2 @@
 pub mod grid;
+pub mod list;

+ 22 - 24
starry_toolkit/src/main.rs

@@ -1,10 +1,12 @@
 use starry_client::base::color::Color;
 use starry_server::core::{SCREEN_HEIGHT, SCREEN_WIDTH};
+use starry_toolkit::base::{
+    panel::{Panel, PanelRendererMode},
+    rect::Rect,
+};
 use starry_toolkit::{
-    base::{panel::Panel, rect::Rect},
-    layout::grid::Grid,
-    traits::text::Text,
-    widgets::label::Label,
+    layout::grid::{Grid, GridArrangeType},
+    widgets::image::Image,
 };
 
 fn main() {
@@ -13,31 +15,27 @@ fn main() {
         "Title",
         Color::rgb(255, 255, 255),
     );
+    // 显示矩形线框
+    panel.set_renderer_mode(PanelRendererMode::WithWireframe);
 
-    let label1 = Label::new();
-    label1.set_text("abc");
-
-    let label2 = Label::new();
-    label2.set_text("....");
-
-    let label3 = Label::new();
-    label3.set_text("12.g");
+    let image1 = Image::new_from_color(32, 32, Color::rgb(128, 255, 255));
+    let image2 = Image::new_from_color(32, 32, Color::rgb(128, 255, 255));
+    let image3 = Image::new_from_color(32, 32, Color::rgb(128, 255, 255));
+    let image4 = Image::new_from_color(32, 32, Color::rgb(128, 255, 255));
+    let image5 = Image::new_from_color(32, 32, Color::rgb(128, 255, 255));
 
     let grid = Grid::new();
-    grid.set_space(10, 10);
-    grid.set_upper_limit(2);
-    grid.add_element(&label1);
-    grid.add_element(&label2);
-    grid.add_element(&label3);
+    grid.set_arrange_type(GridArrangeType::Horizontal); //优先横向排列
+    grid.set_upper_limit(3); //每行最大元素个数为3
 
-    panel.add_child(&grid);
-
-    // // Image
-    // let image = Image::from_path(IMAGE_PATH).unwrap();
-    // image.reposition(0, SCREEN_HEIGHT as i32 / 2);
-    // image.resize(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32 / 2);
-    // panel.add_child(&image);
+    grid.set_space(20, 20);
+    grid.add_element(&image1);
+    grid.add_element(&image2);
+    grid.add_element(&image3);
+    grid.add_element(&image4);
+    grid.add_element(&image5);
 
+    panel.add_child(&grid);
     panel.draw();
 
     // 便于观察结果

+ 22 - 18
starry_toolkit/src/widgets/label.rs

@@ -15,6 +15,7 @@ use crate::{
 
 use super::{PivotType, Widget};
 
+// TODO 支持"调整字体大小以适配"的选项
 #[derive(PartialEq, Copy, Clone)]
 pub enum LabelOverflowType {
     /// 不适配 溢出部分不显示
@@ -23,9 +24,9 @@ pub enum LabelOverflowType {
     ShinkToFit,
     /// 省略多余内容
     Omit,
-    // TODO 支持"调整字体大小以适配"的选项
 }
 
+// TODO 暂不支持自动换行
 pub struct Label {
     self_ref: RefCell<Weak<Label>>,
     rect: Cell<Rect>,
@@ -46,7 +47,6 @@ pub struct Label {
     text_pivot: Cell<PivotType>,
 }
 
-// TODO 暂时只支持渲染一行字体
 impl Label {
     pub fn new() -> Arc<Self> {
         let label = Arc::new(Label {
@@ -76,10 +76,13 @@ impl Label {
         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);
+                self.resize_base(text.len() as u32 * 8 as u32, 16);
             }
+            // 省略溢出的部分
             LabelOverflowType::Omit => {
                 let rect = self.rect.get();
 
@@ -99,10 +102,23 @@ impl Label {
             self.show_text.borrow().len() as u32 * 8,
             16,
         ));
+
+        self.text_rect.set(align_rect(
+            self.text_rect.get(),
+            self.rect.get(),
+            self.text_pivot.get(),
+            Vector2::new(0, 0),
+        ));
     }
 
     pub fn set_adapt_type(&self, adapt_type: LabelOverflowType) {
         self.adapt_type.set(adapt_type);
+        self.handle_overflow();
+        self.arrange_all();
+    }
+
+    pub fn set_text_pivot_type(&self, pivot: PivotType) {
+        self.text_pivot.set(pivot);
     }
 }
 
@@ -169,7 +185,6 @@ impl Widget for Label {
                             current_rect,
                             self.panel().borrow().clone().unwrap().rect(),
                         );
-                        // TODO 应用主题(Theme)颜色
                         renderer.char(local_rect.x, local_rect.y, char, self.text_color.get());
                     } else {
                         println!("[Error] Label do not belong to any panel!");
@@ -204,14 +219,8 @@ impl Widget for Label {
 
     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),
-        ));
+        self.arrange_all();
     }
 
     fn arrange_self(&self) {
@@ -231,13 +240,8 @@ impl Text for Label {
         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.handle_overflow(); //处理文本溢出的情况
+        self.arrange_all();
         self
     }
 

+ 1 - 0
starry_toolkit/src/widgets/mod.rs

@@ -15,6 +15,7 @@ use crate::{
 pub mod image;
 pub mod label;
 
+/// 控件对齐类型
 #[derive(PartialEq, Copy, Clone, Debug)]
 pub enum PivotType {
     /// 不进行对齐 pivot_offset即为世界坐标