Răsfoiți Sursa

完善toolkit库和AssetManager (#13)

R0ronoa 1 an în urmă
părinte
comite
2b942a51ce

+ 20 - 6
starry_applications/src/asset_manager/code/asset_item.rs

@@ -1,7 +1,7 @@
 use std::{
     cell::{Cell, RefCell},
     str::FromStr,
-    sync::Arc,
+    sync::{Arc, Weak},
 };
 
 use starry_client::base::{color::Color, renderer::Renderer};
@@ -12,7 +12,7 @@ use starry_toolkit::{
         align_rect,
         image::Image,
         label::{Label, LabelOverflowType},
-        widget_add_child, PivotType, Widget,
+        PivotType, Widget,
     },
 };
 
@@ -22,11 +22,13 @@ 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 rect: Cell<Rect>,
     pivot: Cell<PivotType>,
     pivot_offset: Cell<Vector2>,
     parent: RefCell<Option<Arc<dyn Widget>>>,
     children: RefCell<Vec<Arc<dyn Widget>>>,
+    panel_rect: Cell<Option<Rect>>,
     /// 缓存值
     cache_focused: Cell<bool>,
     pub file_path: RefCell<String>,
@@ -38,19 +40,23 @@ impl AssetItem {
 
     pub fn new(file_name: &str, is_dir: bool) -> Arc<Self> {
         let item = Arc::new(AssetItem {
+            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)),
-            parent: RefCell::new(None),
             children: RefCell::new(Vec::new()),
+            parent: RefCell::new(None),
+            panel_rect: Cell::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::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());
+        item.add_child(bg);
 
         // 文件图标Image
         if let Some(icon) = match is_dir {
@@ -59,7 +65,7 @@ impl AssetItem {
         } {
             let icon = Image::from_image(icon);
             icon.set_pivot_type(PivotType::Top);
-            widget_add_child(item.clone(), icon.clone());
+            item.add_child(icon);
         }
 
         // 文件名Label
@@ -69,13 +75,17 @@ impl AssetItem {
         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());
+        item.add_child(name);
 
         return item;
     }
 }
 
 impl Widget for AssetItem {
+    fn self_ref(&self) -> Arc<dyn Widget> {
+        self.self_ref.borrow().upgrade().unwrap()
+    }
+
     fn name(&self) -> &str {
         "AssetItem"
     }
@@ -100,6 +110,10 @@ impl Widget for AssetItem {
         &self.children
     }
 
+    fn panel_rect(&self) -> &Cell<Option<Rect>> {
+        &self.panel_rect
+    }
+
     fn draw(&self, renderer: &mut dyn Renderer, focused: bool) {
         if focused != self.cache_focused.get() {
             self.cache_focused.set(focused);

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

@@ -1,5 +1,6 @@
 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};
@@ -7,10 +8,10 @@ use starry_toolkit::{
     base::{panel::Panel, rect::Rect},
     layout::grid::{Grid, GridArrangeType},
     traits::enter::Enter,
-    widgets::{image::Image, widget_add_child},
+    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");
@@ -42,14 +43,26 @@ impl AssetManager {
         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 == ' ' {
+            if char == '\n' {
                 let asset_manager: &mut AssetManager = unsafe { &mut *self_ptr };
 
                 if let Some(item) = asset_manager.items.get(&grid.focused_id.get().unwrap()) {
-                    asset_manager.cur_path.push_str(&item.file_path.borrow());
-                    asset_manager.cur_path.push_str(&"/");
+                    if item.file_path.borrow().eq(&"..".to_string()) {
+                        if asset_manager.cur_path.len() == 1 {
+                            return;
+                        } else {
+                            let split_path =
+                                &asset_manager.cur_path[..asset_manager.cur_path.len() - 1];
+                            let slash_pos = split_path.rfind('/').unwrap();
+                            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(&"/");
+                    }
                     asset_manager.refresh();
                 }
 
@@ -112,8 +125,6 @@ impl AssetManager {
                     .get(&(nxt_row as usize, nxt_col as usize))
                     .unwrap(),
             );
-            grid.focused_id
-                .set(Some((nxt_row as usize, nxt_col as usize)));
 
             *redraw = true;
         });
@@ -129,17 +140,26 @@ impl AssetManager {
         self.items.clear();
         self.asset_grid.clear();
 
+        // 父目录
+        let parent_asset_item = AssetItem::new("..", true);
+        let (row, col) = self.asset_grid.add(&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) {
             for entry in entries {
                 if let Ok(item) = entry {
-                    let asset_item = AssetItem::new(
-                        item.file_name().to_str().unwrap(),
-                        item.metadata().unwrap().is_dir(),
-                    );
+                    let is_dir = if let Ok(metadata) = item.metadata() {
+                        metadata.is_dir()
+                    } else {
+                        false
+                    };
+
+                    let asset_item = AssetItem::new(item.file_name().to_str().unwrap(), is_dir);
                     let (row, col) = self.asset_grid.add(&asset_item);
                     self.items.insert((row, col), asset_item.clone());
-                    widget_add_child(self.asset_grid.clone(), asset_item);
+                    (*self.asset_grid.borrow_mut()).add_child(asset_item);
                 }
             }
         } else {

+ 15 - 13
starry_applications/src/lib.rs

@@ -9,32 +9,34 @@ use std::io;
 use std::os::unix::io::AsRawFd;
 use termios::{tcsetattr, Termios};
 
-// TODO
-#[allow(dead_code)]
-pub fn set_terminal() -> io::Result<()> {
+static mut STORED_SETTINGS: Option<Termios> = None;
+
+/// 禁止tty回显 并设置为非阻塞模式
+pub fn set_tty() -> io::Result<()> {
     let stdin = io::stdin().as_raw_fd();
-    let stored_settings = Termios::from_fd(stdin)?;
+    let cur_settings = Termios::from_fd(stdin)?;
 
-    let mut new_settings = stored_settings.clone();
+    let mut new_settings = cur_settings.clone();
     new_settings.c_lflag &= !(termios::ICANON) & !(termios::ECHO);
     new_settings.c_cc[termios::VMIN] = 1;
     new_settings.c_cc[termios::VTIME] = 0;
 
     tcsetattr(stdin, termios::TCSANOW, &new_settings)?;
 
+    unsafe {
+        STORED_SETTINGS = Some(cur_settings);
+    }
+
     Ok(())
 }
 
-// TODO
-#[allow(dead_code)]
-pub fn reset_terminal() -> io::Result<()> {
+/// 回退tty设置
+pub fn reset_tty() -> io::Result<()> {
     let stdin = io::stdin().as_raw_fd();
-    let stored_settings = Termios::from_fd(stdin)?;
 
-    let mut new_settings = stored_settings.clone();
-    new_settings.c_lflag &= (termios::ICANON) & (termios::ECHO);
-
-    tcsetattr(stdin, termios::TCSANOW, &new_settings)?;
+    unsafe {
+        tcsetattr(stdin, termios::TCSANOW, &STORED_SETTINGS.unwrap())?;
+    }
 
     Ok(())
 }

+ 7 - 4
starry_applications/src/main.rs

@@ -1,11 +1,14 @@
-use starry_apps::asset_manager::code::AssetManager;
+use std::io;
 
-fn main() {
-    // set_terminal();
+use starry_apps::{asset_manager::code::AssetManager, set_tty};
+
+fn main() -> io::Result<()> {
+    set_tty()?;
 
-    // TODO
     let mut viewer = AssetManager::new();
     viewer.init();
     viewer.refresh();
     viewer.exec();
+
+    Ok(())
 }

+ 9 - 4
starry_client/src/window.rs

@@ -16,6 +16,8 @@ const SCREEN_WIDTH: usize = 1440;
 #[allow(dead_code)]
 const SCREEN_HEIGHT: usize = 900;
 
+const FB_FILE_PATH: &str = "/dev/fb0";
+
 /// 客户端的窗口类,与服务端的窗口对象一一对应  
 /// 一般来说客户端应用程序不直接使用该类,而通过Toolkit库间接使用
 #[allow(dead_code)]
@@ -42,6 +44,8 @@ pub struct Window {
     // data_opt: Option<& 'static mut [Color]>,
     /// 窗口的渲染数据
     data_opt: Option<Box<[Color]>>,
+    /// 帧缓冲文件
+    fb_file: File,
 }
 
 impl Renderer for Window {
@@ -62,16 +66,16 @@ impl Renderer for Window {
     }
 
     fn sync(&mut self) -> bool {
-        let mut fb = File::open("/dev/fb0").expect("Unable to open framebuffer");
-
         for y in 0..self.height() as i32 {
             for x in 0..self.width() as i32 {
                 let pixel = self.get_pixel(x, y);
                 let offset = (((y + self.y()) * SCREEN_WIDTH as i32) + x + self.x()) * 4;
                 // 写缓冲区
-                fb.seek(SeekFrom::Start(offset as u64))
+                self.fb_file
+                    .seek(SeekFrom::Start(offset as u64))
                     .expect("Unable to seek framebuffer");
-                fb.write_all(&pixel.to_bgra_bytes())
+                self.fb_file
+                    .write_all(&pixel.to_bgra_bytes())
                     .expect("Unable to write framebuffer");
             }
         }
@@ -98,6 +102,7 @@ impl Window {
             mode: Cell::new(RenderMode::Blend),
             // 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"),
         }
 
         // TODO: 与服务器通信

+ 0 - 2
starry_toolkit/src/base/event.rs

@@ -13,12 +13,10 @@ pub enum Event {
 
     KeyPressed {
         character: Option<char>,
-        scancode: u8,
     },
 
     KeyReleased {
         character: Option<char>,
-        scancode: u8,
     },
 
     Scroll {

+ 28 - 20
starry_toolkit/src/base/panel.rs

@@ -3,6 +3,8 @@ use std::{
     fs::File,
     io::Read,
     sync::Arc,
+    thread,
+    time::Duration,
 };
 
 use starry_client::{
@@ -19,6 +21,8 @@ use super::{event::Event, rect::Rect};
 
 const TTY_DEVICE_PATH: &str = "/dev/char/tty0";
 
+const DURATION_TIME: Duration = Duration::from_millis(25);
+
 /// 面板渲染器
 pub struct PanelRenderer<'a> {
     /// 客户端窗口
@@ -61,17 +65,13 @@ impl<'a> Renderer for PanelRenderer<'a> {
     // }
 }
 
-impl<'a> Drop for PanelRenderer<'a> {
-    fn drop(&mut self) {
-        self.window.sync();
-    }
-}
-
-/// UI面板类作为容器管理一组UI组件(UI-Widget)  
+/// UI面板类作为容器管理一组UI组件(UI-Widget)   
 /// 拥有一个窗口对象用于渲染和事件传递
 pub struct Panel {
     /// 客户端窗口对象
     window: RefCell<Window>,
+    /// 面板矩形
+    rect: Cell<Rect>,
     /// 子组件数组
     pub widgets: RefCell<Vec<Arc<dyn Widget>>>,
     /// 窗口是否打开
@@ -82,28 +82,28 @@ pub struct Panel {
     events: RefCell<Vec<Event>>,
     /// 需要重绘画面
     redraw: bool,
+    /// tty文件
+    tty_file: File,
 }
 
 impl Panel {
     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,
-        ))
+        Panel::from_window(
+            Window::new(rect.x, rect.y, rect.width, rect.height, title, color),
+            rect,
+        )
     }
 
-    pub fn from_window(window: Window) -> Self {
+    pub fn from_window(window: Window, rect: Rect) -> Self {
         Panel {
             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"),
         }
     }
 
@@ -136,6 +136,11 @@ impl Panel {
         (*window).height()
     }
 
+    /// 返回面板矩形
+    pub fn rect(&self) -> Rect {
+        self.rect.get()
+    }
+
     /// 窗口标题
     pub fn title(&self) -> String {
         let window = self.window.borrow();
@@ -170,7 +175,7 @@ impl Panel {
         let mut widgets = self.widgets.borrow_mut();
         let id = widgets.len();
         widgets.push(widget.clone());
-
+        widget.panel_rect().set(Some(self.rect.get()));
         return id;
     }
 
@@ -234,6 +239,8 @@ impl Panel {
             self.polling_tty();
             self.tick();
             self.draw_if_needed();
+
+            thread::sleep(DURATION_TIME);
         }
     }
 
@@ -247,13 +254,14 @@ impl Panel {
 
     // TODO 临时在客户端做输入读取  后续改为由服务器实现
     fn polling_tty(&mut self) {
-        let mut tty = File::open(TTY_DEVICE_PATH).expect("fail to open tty file");
         let mut bufffer: [u8; 128] = [0; 128];
-        let count = tty.read(&mut bufffer).expect("fail to read tty file");
+        let count = self
+            .tty_file
+            .read(&mut bufffer)
+            .expect("[Error] Panel failed to read tty file");
         for i in 0..count {
             self.push_event(Event::KeyPressed {
                 character: Some(bufffer[i] as char),
-                scancode: bufffer[i],
             });
         }
     }

+ 34 - 5
starry_toolkit/src/layout/grid.rs

@@ -1,7 +1,7 @@
 use std::{
     cell::{Cell, RefCell},
     collections::BTreeMap,
-    sync::Arc,
+    sync::{Arc, Weak},
 };
 
 use starry_client::base::renderer::Renderer;
@@ -22,11 +22,13 @@ pub enum GridArrangeType {
 }
 
 pub struct Grid {
+    self_ref: RefCell<Weak<Grid>>,
     pub 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>>,
     /// x坐标间隔
     space_x: Cell<i32>,
     /// y坐标间隔
@@ -55,12 +57,14 @@ pub struct Grid {
 
 impl Grid {
     pub fn new() -> Arc<Self> {
-        Arc::new(Grid {
+        let grid = Arc::new(Grid {
+            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_rect: Cell::new(None),
             space_x: Cell::new(0),
             space_y: Cell::new(0),
             upper_limit: Cell::new(0),
@@ -73,7 +77,11 @@ impl Grid {
             focused_widget: RefCell::new(None),
             arrange_type: Cell::new(GridArrangeType::Vertical),
             enter_callback: RefCell::new(None),
-        })
+        });
+
+        (*grid.self_ref.borrow_mut()) = Arc::downgrade(&grid);
+
+        return grid;
     }
 
     /// 设置每行/列最大元素数量(取决于行/列优先排列)
@@ -146,6 +154,13 @@ impl Grid {
         self
     }
 
+    pub fn focus_by_id(&self, (row, col): (usize, usize)) {
+        if let Some(widget) = self.elements.borrow().get(&(row, col)) {
+            (*self.focused_widget.borrow_mut()) = Some(widget.clone());
+            self.focused_id.set(Some((row, col)));
+        }
+    }
+
     // TODO 注释补充
     pub fn arrange_elements(&self, resize_children: bool) {
         if self.elements.borrow().is_empty() {
@@ -220,6 +235,10 @@ impl Grid {
 }
 
 impl Widget for Grid {
+    fn self_ref(&self) -> Arc<dyn Widget> {
+        self.self_ref.borrow().upgrade().unwrap()
+    }
+
     fn name(&self) -> &str {
         "Grid"
     }
@@ -244,6 +263,10 @@ impl Widget for Grid {
         &self.children
     }
 
+    fn panel_rect(&self) -> &Cell<Option<Rect>> {
+        &self.panel_rect
+    }
+
     fn draw(&self, renderer: &mut dyn Renderer, _focused: bool) {
         for (&(_row, _col), widget) in self.elements.borrow().iter() {
             widget.update();
@@ -278,8 +301,14 @@ impl Focus for Grid {
         self.focused_widget.clone()
     }
 
-    fn focus(&self, widget: &Arc<dyn Widget>) {
-        (*self.focused_widget.borrow_mut()) = Some(widget.clone());
+    fn focus(&self, focused_widget: &Arc<dyn Widget>) {
+        // 同时更新focused_id
+        for ((row, col), widget) in self.elements.borrow().iter() {
+            if Arc::ptr_eq(widget, focused_widget) {
+                self.focused_id.set(Some((*row, *col)));
+                (*self.focused_widget.borrow_mut()) = Some(focused_widget.clone());
+            }
+        }
     }
 }
 

+ 16 - 4
starry_toolkit/src/widgets/image.rs

@@ -1,6 +1,6 @@
 use std::{
     cell::{Cell, RefCell},
-    sync::Arc,
+    sync::{Arc, Weak},
 };
 
 use starry_client::base::{color::Color, renderer::Renderer};
@@ -12,11 +12,13 @@ use super::{PivotType, Widget};
 use crate::starry_server::base::image::Image as ImageResource;
 
 pub struct Image {
+    self_ref: RefCell<Weak<Image>>,
     pub 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>>,
     /// 图像源数据
     pub image: RefCell<ImageResource>,
 }
@@ -36,11 +38,13 @@ impl Image {
 
     pub fn from_image(image: ImageResource) -> Arc<Self> {
         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)),
-            parent: RefCell::new(None),
             children: RefCell::new(vec![]),
+            parent: RefCell::new(None),
+            panel_rect: Cell::new(None),
             image: RefCell::new(image),
         })
     }
@@ -55,6 +59,10 @@ impl Image {
 }
 
 impl Widget for Image {
+    fn self_ref(&self) -> Arc<dyn Widget> {
+        self.self_ref.borrow().upgrade().unwrap()
+    }
+
     fn name(&self) -> &str {
         "Image"
     }
@@ -71,12 +79,16 @@ impl Widget for Image {
         &self.pivot_offset
     }
 
+    fn children(&self) -> &RefCell<Vec<Arc<dyn Widget>>> {
+        &self.children
+    }
+
     fn parent(&self) -> &RefCell<Option<Arc<dyn Widget>>> {
         &self.parent
     }
 
-    fn children(&self) -> &RefCell<Vec<Arc<dyn Widget>>> {
-        &self.children
+    fn panel_rect(&self) -> &Cell<Option<Rect>> {
+        &self.panel_rect
     }
 
     fn draw(&self, renderer: &mut dyn Renderer, _focused: bool) {

+ 21 - 5
starry_toolkit/src/widgets/label.rs

@@ -1,7 +1,7 @@
 use std::{
     cell::{Cell, RefCell},
     cmp::max,
-    sync::Arc,
+    sync::{Arc, Weak},
 };
 
 use starry_client::base::{color::Color, renderer::Renderer};
@@ -25,11 +25,13 @@ pub enum LabelOverflowType {
 }
 
 pub struct Label {
+    self_ref: RefCell<Weak<Label>>,
     pub rect: Cell<Rect>,
     pivot: Cell<PivotType>,
     pivot_offset: Cell<Vector2>,
-    parent: RefCell<Option<Arc<dyn Widget>>>,
     children: RefCell<Vec<Arc<dyn Widget>>>,
+    parent: RefCell<Option<Arc<dyn Widget>>>,
+    panel_rect: Cell<Option<Rect>>,
     /// 实际上的文本
     real_text: RefCell<String>,
     /// 用于显示的文本
@@ -45,19 +47,25 @@ pub struct Label {
 // TODO 暂时只支持渲染一行字体
 impl Label {
     pub fn new() -> Arc<Self> {
-        Arc::new(Label {
+        let label = Arc::new(Label {
             rect: Cell::new(Rect::default()),
             pivot: Cell::new(PivotType::TopLeft),
             pivot_offset: Cell::new(Vector2::new(0, 0)),
-            parent: RefCell::new(None),
             children: RefCell::new(vec![]),
+            parent: RefCell::new(None),
+            panel_rect: Cell::new(None),
             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),
-        })
+            self_ref: RefCell::new(Weak::new()),
+        });
+
+        (*label.self_ref.borrow_mut()) = Arc::downgrade(&label);
+
+        return label;
     }
 
     /// 处理文本溢出的情况
@@ -97,6 +105,10 @@ impl Label {
 }
 
 impl Widget for Label {
+    fn self_ref(&self) -> Arc<dyn Widget> {
+        self.self_ref.borrow().upgrade().unwrap() as Arc<dyn Widget>
+    }
+
     fn name(&self) -> &str {
         "Label"
     }
@@ -121,6 +133,10 @@ impl Widget for Label {
         &self.children
     }
 
+    fn panel_rect(&self) -> &Cell<Option<Rect>> {
+        &self.panel_rect
+    }
+
     fn draw(&self, renderer: &mut dyn Renderer, _focused: bool) {
         let origin_rect = self.text_rect.get();
         let mut current_rect = self.text_rect.get(); // 当前字符渲染矩形

+ 23 - 12
starry_toolkit/src/widgets/mod.rs

@@ -12,12 +12,6 @@ use crate::base::{event::Event, rect::Rect, vector2::Vector2};
 pub mod image;
 pub mod label;
 
-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来进行矩形位置的对齐
 ///
@@ -100,6 +94,9 @@ pub enum PivotType {
 
 ///  UI组件需要实现的特性
 pub trait Widget: Any {
+    /// 返回自身指针
+    fn self_ref(&self) -> Arc<dyn Widget>;
+
     /// 返回渲染的矩形区域
     fn rect(&self) -> &Cell<Rect>;
 
@@ -109,15 +106,24 @@ pub trait Widget: Any {
     /// 基于基准点的偏移量
     fn pivot_offset(&self) -> &Cell<Vector2>;
 
+    /// 所属面板的矩形
+    fn panel_rect(&self) -> &Cell<Option<Rect>>;
+
     /// 返回组件的名字
     fn name(&self) -> &str;
 
-    /// 返回父物体
-    fn parent(&self) -> &RefCell<Option<Arc<dyn Widget>>>;
-
     /// 返回子组件数组
     fn children(&self) -> &RefCell<Vec<Arc<dyn Widget>>>;
 
+    /// 父物体
+    fn parent(&self) -> &RefCell<Option<Arc<dyn Widget>>>;
+
+    /// 添加子物体
+    fn add_child(&self, widget: Arc<dyn Widget>) {
+        self.children().borrow_mut().push(widget.clone());
+        (*widget.parent().borrow_mut()) = Some(self.self_ref());
+    }
+
     /// 渲染组件
     fn draw(&self, renderer: &mut dyn Renderer, focused: bool);
 
@@ -181,12 +187,17 @@ pub trait Widget: Any {
         self.arrange_self_base();
     }
 
-    /// 根据父物体和pivot值来调整自身位置 统一处理 方便覆写
+    /// 根据参考的矩形和pivot值来调整自身位置(默认为父物体,也可以自定义为其他矩形)
+    /// 统一处理 方便覆写
     fn arrange_self_base(&self) {
         let relative_rect: Rect = if self.parent().borrow().is_some() {
-            self.parent().borrow().as_ref().unwrap().rect().get()
+            // 优先以父物体作为参考
+            self.parent().borrow().clone().unwrap().rect().get()
+        } else if self.panel_rect().get().is_some() {
+            // 没有父物体 则以所属面板作为参考
+            self.panel_rect().get().unwrap()
         } else {
-            // 没有父物体 则以整个屏幕作为参考
+            // 则以整个屏幕作为参考
             Rect::new(0, 0, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32)
         };