Browse Source

完善toolkit库和AssetManager (#14)

R0ronoa 3 weeks ago
parent
commit
1bee64b64b

+ 23 - 26
starry_applications/src/asset_manager/code/asset_item.rs

@@ -1,4 +1,5 @@
 use std::{
+    any::Any,
     cell::{Cell, RefCell},
     str::FromStr,
     sync::{Arc, Weak},
@@ -6,10 +7,9 @@ use std::{
 
 use starry_client::base::{color::Color, renderer::Renderer};
 use starry_toolkit::{
-    base::{rect::Rect, vector2::Vector2},
+    base::{panel::Panel, rect::Rect, vector2::Vector2},
     traits::text::Text,
     widgets::{
-        align_rect,
         image::Image,
         label::{Label, LabelOverflowType},
         PivotType, Widget,
@@ -28,10 +28,11 @@ pub struct AssetItem {
     pivot_offset: Cell<Vector2>,
     parent: RefCell<Option<Arc<dyn Widget>>>,
     children: RefCell<Vec<Arc<dyn Widget>>>,
-    panel_rect: Cell<Option<Rect>>,
+    panel: RefCell<Option<Arc<Panel>>>,
     /// 缓存值
     cache_focused: Cell<bool>,
     pub file_path: RefCell<String>,
+    pub is_dir: Cell<bool>,
 }
 
 impl AssetItem {
@@ -46,15 +47,17 @@ impl AssetItem {
             pivot_offset: Cell::new(Vector2::new(0, 0)),
             children: RefCell::new(Vec::new()),
             parent: RefCell::new(None),
-            panel_rect: Cell::new(None),
+            panel: RefCell::new(None),
             cache_focused: Cell::new(false),
             file_path: RefCell::new(String::from_str(file_name).unwrap()),
+            is_dir: Cell::new(is_dir),
         });
 
         (*item.self_ref.borrow_mut()) = Arc::downgrade(&item);
 
         // 背景Image
-        let bg = Image::from_color(Self::ITEM_WIDTH, Self::ITEM_HEIGHT, Color::rgba(0, 0, 0, 0));
+        let bg =
+            Image::new_from_color(Self::ITEM_WIDTH, Self::ITEM_HEIGHT, Color::rgba(0, 0, 0, 0));
         bg.set_pivot_type(PivotType::Center);
         item.add_child(bg);
 
@@ -63,7 +66,7 @@ impl AssetItem {
             true => ImageResource::from_path(DIR_ICON_PATH),
             false => ImageResource::from_path(FILE_ICON_PATH),
         } {
-            let icon = Image::from_image(icon);
+            let icon = Image::new_from_image(icon);
             icon.set_pivot_type(PivotType::Top);
             item.add_child(icon);
         }
@@ -86,6 +89,10 @@ impl Widget for AssetItem {
         self.self_ref.borrow().upgrade().unwrap()
     }
 
+    fn as_any_ref(&self) -> &dyn Any {
+        self
+    }
+
     fn name(&self) -> &str {
         "AssetItem"
     }
@@ -110,8 +117,8 @@ impl Widget for AssetItem {
         &self.children
     }
 
-    fn panel_rect(&self) -> &Cell<Option<Rect>> {
-        &self.panel_rect
+    fn panel(&self) -> &RefCell<Option<Arc<Panel>>> {
+        &self.panel
     }
 
     fn draw(&self, renderer: &mut dyn Renderer, focused: bool) {
@@ -119,27 +126,17 @@ impl Widget for AssetItem {
             self.cache_focused.set(focused);
 
             // 如果当前被选中,则背景高亮
-            let mut children = self.children.borrow_mut();
+            let children = self.children.borrow_mut();
+            let bg_image = children[0].self_ref();
+            let bg_image = bg_image
+                .as_any_ref()
+                .downcast_ref::<Image>()
+                .expect("[Error] AssetItem failed to cast widget to image");
             if focused {
-                children[0] = Image::from_color(
-                    Self::ITEM_WIDTH,
-                    Self::ITEM_HEIGHT,
-                    Color::rgba(0, 255, 255, 64),
-                );
+                bg_image.set_from_color(Color::rgba(0, 255, 255, 64));
             } else {
-                children[0] =
-                    Image::from_color(Self::ITEM_WIDTH, Self::ITEM_HEIGHT, Color::rgba(0, 0, 0, 0));
+                bg_image.set_from_color(Color::rgba(0, 0, 0, 0));
             }
-
-            // TODO
-            // children[0].set_pivot_type(PivotType::Center);
-
-            children[0].rect().set(align_rect(
-                children[0].rect().get(),
-                self.rect.get(),
-                PivotType::Center,
-                Vector2::new(0, 0),
-            ))
         }
 
         for child in self.children.borrow().iter() {

+ 41 - 12
starry_applications/src/asset_manager/code/mod.rs

@@ -1,6 +1,5 @@
 use self::asset_item::AssetItem;
 use crate::starry_toolkit::traits::focus::Focus;
-use crate::starry_toolkit::widgets::Widget;
 use starry_client::base::color::Color;
 use starry_server::base::image::Image as ImageResource;
 use starry_server::core::{SCREEN_HEIGHT, SCREEN_WIDTH};
@@ -10,17 +9,22 @@ use starry_toolkit::{
     traits::enter::Enter,
     widgets::image::Image,
 };
-use std::borrow::BorrowMut;
 use std::{collections::BTreeMap, fs, sync::Arc};
 pub mod asset_item;
 
 const DESKTOP_BG_PATH: &[u8] = include_bytes!("../resource/desktop_bg.png");
+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>>,
-    panel: Panel,
+    panel: Arc<Panel>,
+    // 原则上一个应用程序对应一个Panel和Window
+    // 这里额外创建一个Panel用于Loading图标优先显示
+    // 后续通过Server来显示Loading或不显示Loading
+    loading_panel: Arc<Panel>,
+    init_show: bool,
 }
 
 impl AssetManager {
@@ -34,22 +38,36 @@ impl AssetManager {
                 "Title",
                 Color::rgb(0, 0, 0),
             ),
+            loading_panel: Panel::new(
+                Rect::new(SCREEN_WIDTH as i32 - 64, SCREEN_HEIGHT as i32 - 64, 64, 64),
+                "Loading",
+                Color::rgb(255, 255, 255),
+            ),
+            init_show: true,
         }
     }
 
     pub fn init(&mut self) {
+        self.init_loading_panel();
+
         let grid = self.asset_grid.clone();
-        grid.set_upper_limit(5);
+        grid.set_upper_limit(8);
         grid.set_space(20, 20);
         grid.set_arrange_type(GridArrangeType::Horizontal);
 
         // 处理输入回调
         let self_ptr = self as *mut AssetManager;
         grid.set_enter_callback(move |grid, char, redraw| {
-            if char == '\n' {
-                let asset_manager: &mut AssetManager = unsafe { &mut *self_ptr };
+            let asset_manager: &mut AssetManager = unsafe { &mut *self_ptr };
 
+            if char == '\n' {
                 if let Some(item) = asset_manager.items.get(&grid.focused_id.get().unwrap()) {
+                    // 判断是否是文件夹
+                    if item.is_dir.get() == false {
+                        return;
+                    }
+
+                    // 返回上级目录
                     if item.file_path.borrow().eq(&"..".to_string()) {
                         if asset_manager.cur_path.len() == 1 {
                             return;
@@ -60,6 +78,7 @@ impl AssetManager {
                             let _ = asset_manager.cur_path.split_off(slash_pos + 1);
                         }
                     } else {
+                        // 打开文件夹
                         asset_manager.cur_path.push_str(&item.file_path.borrow());
                         asset_manager.cur_path.push_str(&"/");
                     }
@@ -126,25 +145,31 @@ impl AssetManager {
                     .unwrap(),
             );
 
-            *redraw = true;
+            asset_manager.loading_panel.draw();
+            redraw.set(true);
         });
 
-        self.panel.add_child(&Image::from_image(
+        self.panel.add_child(&Image::new_from_image(
             ImageResource::from_path(DESKTOP_BG_PATH).unwrap(),
         ));
 
         self.panel.add_child(&(self.asset_grid));
     }
 
+    fn init_loading_panel(&mut self) {
+        self.loading_panel.add_child(&Image::new_from_image(
+            ImageResource::from_path(LOADING_IMG_PATH).unwrap(),
+        ));
+    }
+
     pub fn refresh(&mut self) {
         self.items.clear();
         self.asset_grid.clear();
 
         // 父目录
         let parent_asset_item = AssetItem::new("..", true);
-        let (row, col) = self.asset_grid.add(&parent_asset_item);
+        let (row, col) = self.asset_grid.add_element(&parent_asset_item);
         self.items.insert((row, col), parent_asset_item.clone());
-        (*self.asset_grid.borrow_mut()).add_child(parent_asset_item);
 
         // 读取目录中的文件列表
         if let Ok(entries) = fs::read_dir(&self.cur_path) {
@@ -157,9 +182,8 @@ impl AssetManager {
                     };
 
                     let asset_item = AssetItem::new(item.file_name().to_str().unwrap(), is_dir);
-                    let (row, col) = self.asset_grid.add(&asset_item);
+                    let (row, col) = self.asset_grid.add_element(&asset_item);
                     self.items.insert((row, col), asset_item.clone());
-                    (*self.asset_grid.borrow_mut()).add_child(asset_item);
                 }
             }
         } else {
@@ -175,6 +199,11 @@ impl AssetManager {
             grid.focus(widget);
         }
 
+        if self.init_show == true {
+            self.init_show = false
+        } else {
+            self.loading_panel.draw();
+        }
         self.panel.draw();
     }
 

BIN
starry_applications/src/asset_manager/resource/loading.png


+ 4 - 4
starry_applications/src/main.rs

@@ -5,10 +5,10 @@ use starry_apps::{asset_manager::code::AssetManager, set_tty};
 fn main() -> io::Result<()> {
     set_tty()?;
 
-    let mut viewer = AssetManager::new();
-    viewer.init();
-    viewer.refresh();
-    viewer.exec();
+    let mut asset_manager = AssetManager::new();
+    asset_manager.init();
+    asset_manager.refresh();
+    asset_manager.exec();
 
     Ok(())
 }

+ 4 - 4
starry_client/src/base/renderer.rs

@@ -389,8 +389,8 @@ pub trait Renderer {
     /// 在指定位置绘制字符
     ///
     /// ## 参数
-    /// - x: x坐标
-    /// - y: y坐标
+    /// - x: x坐标(局部坐标)
+    /// - y: y坐标(局部坐标)
     /// - c: 待绘制的字符
     /// - color: 字符颜色
     fn char(&mut self, x: i32, y: i32, c: char, color: Color) {
@@ -416,8 +416,8 @@ pub trait Renderer {
     /// 在指定位置绘制一幅图像至帧缓冲区
     ///
     /// ## 参数
-    /// - start_x: 起始x坐标(左上角)
-    /// - start_y: 起始y坐标(左上角)
+    /// - start_x: 起始x坐标(局部坐标)
+    /// - start_y: 起始y坐标(局部坐标)
     /// - w: 图像宽度
     /// - h: 图像高度
     /// - data: 图像数据

+ 7 - 2
starry_client/src/window.rs

@@ -1,5 +1,6 @@
 use std::{
     cell::Cell,
+    cmp::min,
     fs::File,
     io::{Seek, SeekFrom, Write},
 };
@@ -66,8 +67,12 @@ impl Renderer for Window {
     }
 
     fn sync(&mut self) -> bool {
-        for y in 0..self.height() as i32 {
-            for x in 0..self.width() as i32 {
+        // 处理窗口大小超限的情况
+        let width = min(self.width() as i32, SCREEN_WIDTH as i32 - self.x);
+        let height = min(self.height() as i32, SCREEN_HEIGHT as i32 - self.y);
+
+        for y in 0..height as i32 {
+            for x in 0..width as i32 {
                 let pixel = self.get_pixel(x, y);
                 let offset = (((y + self.y()) * SCREEN_WIDTH as i32) + x + self.x()) * 4;
                 // 写缓冲区

+ 47 - 38
starry_toolkit/src/base/panel.rs

@@ -1,12 +1,3 @@
-use std::{
-    cell::{Cell, RefCell},
-    fs::File,
-    io::Read,
-    sync::Arc,
-    thread,
-    time::Duration,
-};
-
 use starry_client::{
     base::{
         color::Color,
@@ -14,8 +5,16 @@ use starry_client::{
     },
     window::Window,
 };
+use std::{
+    cell::{Cell, RefCell},
+    fs::File,
+    io::Read,
+    sync::{Arc, Weak},
+    thread,
+    time::Duration,
+};
 
-use crate::{traits::focus::Focus, widgets::Widget};
+use crate::{traits::focus::Focus, util::widget_set_panel, widgets::Widget};
 
 use super::{event::Event, rect::Rect};
 
@@ -68,43 +67,52 @@ impl<'a> Renderer for PanelRenderer<'a> {
 /// UI面板类作为容器管理一组UI组件(UI-Widget)   
 /// 拥有一个窗口对象用于渲染和事件传递
 pub struct Panel {
+    /// 指向自身的弱引用
+    self_ref: RefCell<Weak<Panel>>,
     /// 客户端窗口对象
     window: RefCell<Window>,
     /// 面板矩形
     rect: Cell<Rect>,
     /// 子组件数组
-    pub widgets: RefCell<Vec<Arc<dyn Widget>>>,
+    widgets: RefCell<Vec<Arc<dyn Widget>>>,
     /// 窗口是否打开
-    pub running: Cell<bool>,
+    running: Cell<bool>,
     /// 当前聚焦的窗口
-    pub focused_widget: RefCell<Option<Arc<dyn Widget>>>,
+    focused_widget: RefCell<Option<Arc<dyn Widget>>>,
     /// 事件数组
     events: RefCell<Vec<Event>>,
     /// 需要重绘画面
-    redraw: bool,
+    redraw: Cell<bool>,
     /// tty文件
-    tty_file: File,
+    tty_file: RefCell<File>,
 }
 
 impl Panel {
-    pub fn new(rect: Rect, title: &str, color: Color) -> Self {
+    pub fn new(rect: Rect, title: &str, color: Color) -> Arc<Panel> {
         Panel::from_window(
             Window::new(rect.x, rect.y, rect.width, rect.height, title, color),
             rect,
         )
     }
 
-    pub fn from_window(window: Window, rect: Rect) -> Self {
-        Panel {
+    pub fn from_window(window: Window, rect: Rect) -> Arc<Panel> {
+        let panel = Arc::new(Panel {
+            self_ref: RefCell::new(Weak::default()),
             window: RefCell::new(window),
             rect: Cell::new(rect),
             widgets: RefCell::new(Vec::new()),
             running: Cell::new(true),
             focused_widget: RefCell::new(None),
             events: RefCell::new(Vec::new()),
-            redraw: true,
-            tty_file: File::open(TTY_DEVICE_PATH).expect("[Error] Panel failed to open tty file"),
-        }
+            redraw: Cell::new(false),
+            tty_file: RefCell::new(
+                File::open(TTY_DEVICE_PATH).expect("[Error] Panel failed to open tty file"),
+            ),
+        });
+
+        (*panel.self_ref.borrow_mut()) = Arc::downgrade(&panel);
+
+        return panel;
     }
 
     /// 获得客户端窗口对象
@@ -172,10 +180,15 @@ impl Panel {
 
     /// 添加子组件,返回子组件id
     pub fn add_child<T: Widget>(&self, widget: &Arc<T>) -> usize {
+        widget_set_panel(
+            &widget.self_ref(),
+            &self.self_ref.borrow().upgrade().unwrap(),
+        );
+
         let mut widgets = self.widgets.borrow_mut();
         let id = widgets.len();
         widgets.push(widget.clone());
-        widget.panel_rect().set(Some(self.rect.get()));
+        widget.arrange_all();
         return id;
     }
 
@@ -202,27 +215,22 @@ impl Panel {
         }
     }
 
-    pub fn tick(&mut self) {
+    pub fn tick(&self) {
         // TODO 通过服务器,先从Window对象接收事件,再进行处理
         self.handle_events();
     }
 
     /// 将事件传递给Widget对象
-    fn handle_events(&mut self) {
+    fn handle_events(&self) {
         while let Some(event) = self.events.borrow_mut().pop() {
             // 事件是否已被处理
-            let mut caught = false;
+            let caught = Cell::new(false);
 
             for widget in self.widgets.borrow().iter().rev() {
                 // TODO 处理返回值
-                widget.handle_event(
-                    event,
-                    self.is_focused(widget),
-                    &mut self.redraw,
-                    &mut caught,
-                );
-
-                if caught {
+                widget.handle_event(event, self.is_focused(widget), &self.redraw, &caught);
+
+                if caught.get() {
                     break;
                 }
             }
@@ -234,7 +242,7 @@ impl Panel {
         self.events.borrow_mut().push(event);
     }
 
-    pub fn exec(&mut self) {
+    pub fn exec(&self) {
         while self.running.get() {
             self.polling_tty();
             self.tick();
@@ -245,18 +253,19 @@ impl Panel {
     }
 
     /// 必要时重绘
-    fn draw_if_needed(&mut self) {
-        if self.redraw {
+    fn draw_if_needed(&self) {
+        if self.redraw.get() {
             self.draw();
-            self.redraw = false;
+            self.redraw.set(false);
         }
     }
 
     // TODO 临时在客户端做输入读取  后续改为由服务器实现
-    fn polling_tty(&mut self) {
+    fn polling_tty(&self) {
         let mut bufffer: [u8; 128] = [0; 128];
         let count = self
             .tty_file
+            .borrow_mut()
             .read(&mut bufffer)
             .expect("[Error] Panel failed to read tty file");
         for i in 0..count {

+ 24 - 15
starry_toolkit/src/layout/grid.rs

@@ -1,4 +1,5 @@
 use std::{
+    any::Any,
     cell::{Cell, RefCell},
     collections::BTreeMap,
     sync::{Arc, Weak},
@@ -7,7 +8,7 @@ use std::{
 use starry_client::base::renderer::Renderer;
 
 use crate::{
-    base::{event::Event, rect::Rect, vector2::Vector2},
+    base::{event::Event, panel::Panel, rect::Rect, vector2::Vector2},
     traits::{enter::Enter, focus::Focus},
     widgets::{PivotType, Widget},
 };
@@ -21,14 +22,15 @@ pub enum GridArrangeType {
     Vertical,
 }
 
+// TODO 所有字段限制为私有
 pub struct Grid {
     self_ref: RefCell<Weak<Grid>>,
-    pub rect: Cell<Rect>,
+    rect: Cell<Rect>,
     pivot: Cell<PivotType>,
     pivot_offset: Cell<Vector2>,
     children: RefCell<Vec<Arc<dyn Widget>>>,
     parent: RefCell<Option<Arc<dyn Widget>>>,
-    panel_rect: Cell<Option<Rect>>,
+    panel: RefCell<Option<Arc<Panel>>>,
     /// x坐标间隔
     space_x: Cell<i32>,
     /// y坐标间隔
@@ -36,9 +38,9 @@ pub struct Grid {
     /// 每行/列的最大元素数
     upper_limit: Cell<usize>,
     /// 当前行数
-    pub current_row: Cell<usize>,
+    current_row: Cell<usize>,
     /// 当前列数
-    pub current_column: Cell<usize>,
+    current_column: Cell<usize>,
     /// 当前最大行数
     pub max_row: Cell<usize>,
     /// 当前最大列数
@@ -52,7 +54,7 @@ pub struct Grid {
     /// 优先排列方式
     arrange_type: Cell<GridArrangeType>,
     /// 键盘输入回调
-    enter_callback: RefCell<Option<Arc<dyn Fn(&Self, char, &mut bool)>>>,
+    enter_callback: RefCell<Option<Arc<dyn Fn(&Self, char, &Cell<bool>)>>>,
 }
 
 impl Grid {
@@ -64,7 +66,7 @@ impl Grid {
             pivot_offset: Cell::new(Vector2::new(0, 0)),
             children: RefCell::new(vec![]),
             parent: RefCell::new(None),
-            panel_rect: Cell::new(None),
+            panel: RefCell::new(None),
             space_x: Cell::new(0),
             space_y: Cell::new(0),
             upper_limit: Cell::new(0),
@@ -95,7 +97,9 @@ impl Grid {
         self
     }
 
-    pub fn add<T: Widget>(&self, element: &Arc<T>) -> (usize, usize) {
+    pub fn add_element<T: Widget>(&self, element: &Arc<T>) -> (usize, usize) {
+        self.add_child(element.self_ref());
+
         self.find_next_slot();
         self.elements.borrow_mut().insert(
             (self.current_row.get(), self.current_column.get()),
@@ -141,6 +145,7 @@ impl Grid {
             .borrow_mut()
             .insert((row, column), element.clone());
 
+        self.add_child(element.self_ref());
         self.arrange_elements(false);
     }
 
@@ -239,6 +244,10 @@ impl Widget for Grid {
         self.self_ref.borrow().upgrade().unwrap()
     }
 
+    fn as_any_ref(&self) -> &dyn Any {
+        self
+    }
+
     fn name(&self) -> &str {
         "Grid"
     }
@@ -263,8 +272,8 @@ impl Widget for Grid {
         &self.children
     }
 
-    fn panel_rect(&self) -> &Cell<Option<Rect>> {
-        &self.panel_rect
+    fn panel(&self) -> &RefCell<Option<Arc<Panel>>> {
+        &self.panel
     }
 
     fn draw(&self, renderer: &mut dyn Renderer, _focused: bool) {
@@ -278,8 +287,8 @@ impl Widget for Grid {
         &self,
         event: Event,
         _focused: bool,
-        redraw: &mut bool,
-        caught: &mut bool,
+        redraw: &Cell<bool>,
+        caught: &Cell<bool>,
     ) -> bool {
         match event {
             Event::KeyPressed { character, .. } => {
@@ -287,7 +296,7 @@ impl Widget for Grid {
                     self.emit_enter(character, redraw);
                 }
 
-                *caught = true;
+                caught.set(true);
             }
             // TODO
             _ => {}
@@ -313,13 +322,13 @@ impl Focus for Grid {
 }
 
 impl Enter for Grid {
-    fn emit_enter(&self, char: char, redraw: &mut bool) {
+    fn emit_enter(&self, char: char, redraw: &Cell<bool>) {
         if let Some(ref enter_callback) = *self.enter_callback.borrow() {
             enter_callback(self, char, redraw);
         }
     }
 
-    fn set_enter_callback<T: Fn(&Self, char, &mut bool) + 'static>(&self, func: T) {
+    fn set_enter_callback<T: Fn(&Self, char, &Cell<bool>) + 'static>(&self, func: T) {
         (*self.enter_callback.borrow_mut()) = Some(Arc::new(func));
     }
 }

+ 1 - 0
starry_toolkit/src/lib.rs

@@ -4,4 +4,5 @@ extern crate starry_server;
 pub mod base;
 pub mod layout;
 pub mod traits;
+pub mod util;
 pub mod widgets;

+ 3 - 3
starry_toolkit/src/main.rs

@@ -26,9 +26,9 @@ fn main() {
     let grid = Grid::new();
     grid.set_space(10, 10);
     grid.set_upper_limit(2);
-    grid.add(&label1);
-    grid.add(&label2);
-    grid.add(&label3);
+    grid.add_element(&label1);
+    grid.add_element(&label2);
+    grid.add_element(&label3);
 
     panel.add_child(&grid);
 

+ 4 - 2
starry_toolkit/src/traits/enter.rs

@@ -1,6 +1,8 @@
+use std::cell::Cell;
+
 pub trait Enter {
     /// 调用键盘输入回调
-    fn emit_enter(&self, char: char, redraw: &mut bool);
+    fn emit_enter(&self, char: char, redraw: &Cell<bool>);
     /// 设置回调函数
-    fn set_enter_callback<T: Fn(&Self, char, &mut bool) + 'static>(&self, func: T);
+    fn set_enter_callback<T: Fn(&Self, char, &Cell<bool>) + 'static>(&self, func: T);
 }

+ 89 - 0
starry_toolkit/src/util/mod.rs

@@ -0,0 +1,89 @@
+use std::sync::Arc;
+
+use crate::{
+    base::{panel::Panel, rect::Rect, vector2::Vector2},
+    widgets::{PivotType, Widget},
+};
+/// # 函数功能
+/// 根据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,
+    )
+}
+
+/// # 函数功能
+/// 获得局部坐标系下的矩形区域
+///
+/// ## 参数
+/// source_rect: 原来的矩形区域
+/// target_rect: 作为参考的矩形
+///
+/// ## 返回值
+/// 根据参考计算局部位置后的矩形区域
+pub fn get_local_rect(source_rect: Rect, target_rect: Rect) -> Rect {
+    Rect::new(
+        source_rect.x - target_rect.x,
+        source_rect.y - target_rect.y,
+        source_rect.width,
+        source_rect.height,
+    )
+}
+
+// TODO 注释补充
+pub fn widget_set_panel(widget: &Arc<dyn Widget>, panel: &Arc<Panel>) {
+    (*widget.panel().borrow_mut()) = Some(panel.clone());
+
+    for child in widget.children().borrow().iter() {
+        widget_set_panel(child, panel);
+    }
+}

+ 56 - 17
starry_toolkit/src/widgets/image.rs

@@ -1,11 +1,15 @@
 use std::{
+    any::Any,
     cell::{Cell, RefCell},
     sync::{Arc, Weak},
 };
 
 use starry_client::base::{color::Color, renderer::Renderer};
 
-use crate::base::{rect::Rect, vector2::Vector2};
+use crate::{
+    base::{panel::Panel, rect::Rect, vector2::Vector2},
+    util::get_local_rect,
+};
 
 use super::{PivotType, Widget};
 
@@ -13,49 +17,69 @@ use crate::starry_server::base::image::Image as ImageResource;
 
 pub struct Image {
     self_ref: RefCell<Weak<Image>>,
-    pub rect: Cell<Rect>,
+    rect: Cell<Rect>,
     pivot: Cell<PivotType>,
     pivot_offset: Cell<Vector2>,
     children: RefCell<Vec<Arc<dyn Widget>>>,
     parent: RefCell<Option<Arc<dyn Widget>>>,
-    panel_rect: Cell<Option<Rect>>,
+    panel: RefCell<Option<Arc<Panel>>>,
     /// 图像源数据
-    pub image: RefCell<ImageResource>,
+    image: RefCell<ImageResource>,
 }
 
 impl Image {
     pub fn new(width: u32, height: u32) -> Arc<Self> {
-        Self::from_image(ImageResource::new(width as i32, height as i32))
+        Self::new_from_image(ImageResource::new(width as i32, height as i32))
     }
 
-    pub fn from_color(width: u32, height: u32, color: Color) -> Arc<Self> {
-        Self::from_image(ImageResource::from_color(
+    pub fn new_from_color(width: u32, height: u32, color: Color) -> Arc<Self> {
+        Self::new_from_image(ImageResource::from_color(
             width as i32,
             height as i32,
             color,
         ))
     }
 
-    pub fn from_image(image: ImageResource) -> Arc<Self> {
-        Arc::new(Image {
+    pub fn new_from_image(image: ImageResource) -> Arc<Self> {
+        let image = Arc::new(Image {
             self_ref: RefCell::new(Weak::default()),
             rect: Cell::new(Rect::new(0, 0, image.width() as u32, image.height() as u32)),
             pivot: Cell::new(PivotType::TopLeft),
             pivot_offset: Cell::new(Vector2::new(0, 0)),
             children: RefCell::new(vec![]),
             parent: RefCell::new(None),
-            panel_rect: Cell::new(None),
+            panel: RefCell::new(None),
             image: RefCell::new(image),
-        })
+        });
+
+        (*image.self_ref.borrow_mut()) = Arc::downgrade(&image);
+
+        return image;
     }
 
-    pub fn from_path(path: &[u8]) -> Option<Arc<Self>> {
+    pub fn new_from_path(path: &[u8]) -> Option<Arc<Self>> {
         if let Some(image) = ImageResource::from_path(path) {
-            Some(Self::from_image(image))
+            Some(Self::new_from_image(image))
         } else {
             None
         }
     }
+
+    pub fn set_from_path(&self, path: &[u8]) {
+        if let Some(image) = ImageResource::from_path(path) {
+            (*self.image.borrow_mut()) = image;
+        } else {
+            println!("[Error] Image failed to set image");
+        }
+    }
+
+    pub fn set_from_color(&self, color: Color) {
+        (*self.image.borrow_mut()) = ImageResource::from_color(
+            self.rect.get().width as i32,
+            self.rect.get().height as i32,
+            color,
+        );
+    }
 }
 
 impl Widget for Image {
@@ -63,6 +87,10 @@ impl Widget for Image {
         self.self_ref.borrow().upgrade().unwrap()
     }
 
+    fn as_any_ref(&self) -> &dyn Any {
+        self
+    }
+
     fn name(&self) -> &str {
         "Image"
     }
@@ -87,13 +115,24 @@ impl Widget for Image {
         &self.parent
     }
 
-    fn panel_rect(&self) -> &Cell<Option<Rect>> {
-        &self.panel_rect
+    fn panel(&self) -> &RefCell<Option<Arc<Panel>>> {
+        &self.panel
     }
 
     fn draw(&self, renderer: &mut dyn Renderer, _focused: bool) {
-        let rect = self.rect.get();
         let image = self.image.borrow();
-        renderer.image(rect.x, rect.y, rect.width, rect.height, image.data());
+        if self.panel().borrow().is_some() {
+            let panel_rect = self.panel.borrow().clone().unwrap().rect();
+            let local_rect = get_local_rect(self.rect.get(), panel_rect);
+            renderer.image(
+                local_rect.x,
+                local_rect.y,
+                local_rect.width,
+                local_rect.height,
+                image.data(),
+            );
+        } else {
+            println!("[Error] Image do not belong to any panel!");
+        }
     }
 }

+ 23 - 9
starry_toolkit/src/widgets/label.rs

@@ -1,4 +1,5 @@
 use std::{
+    any::Any,
     cell::{Cell, RefCell},
     cmp::max,
     sync::{Arc, Weak},
@@ -7,11 +8,12 @@ use std::{
 use starry_client::base::{color::Color, renderer::Renderer};
 
 use crate::{
-    base::{rect::Rect, vector2::Vector2},
+    base::{panel::Panel, rect::Rect, vector2::Vector2},
     traits::text::Text,
+    util::{align_rect, get_local_rect},
 };
 
-use super::{align_rect, PivotType, Widget};
+use super::{PivotType, Widget};
 
 #[derive(PartialEq, Copy, Clone)]
 pub enum LabelOverflowType {
@@ -26,12 +28,12 @@ pub enum LabelOverflowType {
 
 pub struct Label {
     self_ref: RefCell<Weak<Label>>,
-    pub rect: Cell<Rect>,
+    rect: Cell<Rect>,
     pivot: Cell<PivotType>,
     pivot_offset: Cell<Vector2>,
     children: RefCell<Vec<Arc<dyn Widget>>>,
     parent: RefCell<Option<Arc<dyn Widget>>>,
-    panel_rect: Cell<Option<Rect>>,
+    panel: RefCell<Option<Arc<Panel>>>,
     /// 实际上的文本
     real_text: RefCell<String>,
     /// 用于显示的文本
@@ -53,7 +55,7 @@ impl Label {
             pivot_offset: Cell::new(Vector2::new(0, 0)),
             children: RefCell::new(vec![]),
             parent: RefCell::new(None),
-            panel_rect: Cell::new(None),
+            panel: RefCell::new(None),
             real_text: RefCell::new(String::new()),
             show_text: RefCell::new(String::new()),
             text_color: Cell::new(Color::rgb(0, 0, 0)), // 默认黑色字体
@@ -109,6 +111,10 @@ impl Widget for Label {
         self.self_ref.borrow().upgrade().unwrap() as Arc<dyn Widget>
     }
 
+    fn as_any_ref(&self) -> &dyn Any {
+        self
+    }
+
     fn name(&self) -> &str {
         "Label"
     }
@@ -133,8 +139,8 @@ impl Widget for Label {
         &self.children
     }
 
-    fn panel_rect(&self) -> &Cell<Option<Rect>> {
-        &self.panel_rect
+    fn panel(&self) -> &RefCell<Option<Arc<crate::base::panel::Panel>>> {
+        &self.panel
     }
 
     fn draw(&self, renderer: &mut dyn Renderer, _focused: bool) {
@@ -158,8 +164,16 @@ 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, self.text_color.get());
+                    if self.panel().borrow().is_some() {
+                        let local_rect = get_local_rect(
+                            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!");
+                    }
                 }
                 current_rect.x += 8;
             }

+ 21 - 64
starry_toolkit/src/widgets/mod.rs

@@ -7,68 +7,15 @@ use std::{
 use starry_client::base::renderer::Renderer;
 use starry_server::core::{SCREEN_HEIGHT, SCREEN_WIDTH};
 
-use crate::base::{event::Event, rect::Rect, vector2::Vector2};
+use crate::{
+    base::{event::Event, panel::Panel, rect::Rect, vector2::Vector2},
+    util::{align_rect, widget_set_panel},
+};
 
 pub mod image;
 pub mod label;
 
-/// # 函数功能
-/// 工具类 根据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)]
+#[derive(PartialEq, Copy, Clone, Debug)]
 pub enum PivotType {
     /// 不进行对齐 pivot_offset即为世界坐标
     None,
@@ -97,6 +44,9 @@ pub trait Widget: Any {
     /// 返回自身指针
     fn self_ref(&self) -> Arc<dyn Widget>;
 
+    /// 返回Any引用
+    fn as_any_ref(&self) -> &dyn Any;
+
     /// 返回渲染的矩形区域
     fn rect(&self) -> &Cell<Rect>;
 
@@ -106,8 +56,8 @@ pub trait Widget: Any {
     /// 基于基准点的偏移量
     fn pivot_offset(&self) -> &Cell<Vector2>;
 
-    /// 所属面板的矩形
-    fn panel_rect(&self) -> &Cell<Option<Rect>>;
+    /// 所属面板
+    fn panel(&self) -> &RefCell<Option<Arc<Panel>>>;
 
     /// 返回组件的名字
     fn name(&self) -> &str;
@@ -121,7 +71,14 @@ pub trait Widget: Any {
     /// 添加子物体
     fn add_child(&self, widget: Arc<dyn Widget>) {
         self.children().borrow_mut().push(widget.clone());
+
+        // 赋值父物体
         (*widget.parent().borrow_mut()) = Some(self.self_ref());
+
+        // 赋值所属的面板
+        if self.panel().borrow().is_some() {
+            widget_set_panel(&widget, &self.panel().borrow().clone().unwrap());
+        }
     }
 
     /// 渲染组件
@@ -135,8 +92,8 @@ pub trait Widget: Any {
         &self,
         _event: Event,
         _focused: bool,
-        _redraw: &mut bool,
-        _caught: &mut bool,
+        _redraw: &Cell<bool>,
+        _caught: &Cell<bool>,
     ) -> bool {
         false
     }
@@ -193,9 +150,9 @@ pub trait Widget: Any {
         let relative_rect: Rect = if self.parent().borrow().is_some() {
             // 优先以父物体作为参考
             self.parent().borrow().clone().unwrap().rect().get()
-        } else if self.panel_rect().get().is_some() {
+        } else if self.panel().borrow().is_some() {
             // 没有父物体 则以所属面板作为参考
-            self.panel_rect().get().unwrap()
+            self.panel().borrow().clone().unwrap().rect()
         } else {
             // 否则以整个屏幕作为参考
             Rect::new(0, 0, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32)