瀏覽代碼

toolkit库引入基本的事件处理框架 资源管理器初步实现交互 (#11)

R0ronoa 1 年之前
父節點
當前提交
8a41b76c57

+ 2 - 1
starry_applications/Cargo.toml

@@ -10,4 +10,5 @@ authors = [ "2447742618 <2447742618@qq.com>" ]
 [dependencies]
 starry_client = {path = "../starry_client" }
 starry_toolkit = {path = "../starry_toolkit" }
-starry_server = {path = "../starry_server" }
+starry_server = {path = "../starry_server" }
+termios = "0.3"

+ 14 - 3
starry_applications/src/asset_manager/code/asset_item.rs

@@ -9,6 +9,7 @@ use starry_toolkit::{
     base::{rect::Rect, vector2::Vector2},
     traits::text::Text,
     widgets::{
+        align_rect,
         image::Image,
         label::{Label, LabelOverflowType},
         widget_add_child, PivotType, Widget,
@@ -28,7 +29,7 @@ pub struct AssetItem {
     children: RefCell<Vec<Arc<dyn Widget>>>,
     /// 缓存值
     cache_focused: Cell<bool>,
-    _file_path: RefCell<String>,
+    pub file_path: RefCell<String>,
 }
 
 impl AssetItem {
@@ -43,7 +44,7 @@ impl AssetItem {
             parent: RefCell::new(None),
             children: RefCell::new(Vec::new()),
             cache_focused: Cell::new(false),
-            _file_path: RefCell::new(String::from_str(file_name).unwrap()),
+            file_path: RefCell::new(String::from_str(file_name).unwrap()),
         });
 
         // 背景Image
@@ -109,12 +110,22 @@ impl Widget for AssetItem {
                 children[0] = Image::from_color(
                     Self::ITEM_WIDTH,
                     Self::ITEM_HEIGHT,
-                    Color::rgba(0, 255, 255, 128),
+                    Color::rgba(0, 255, 255, 64),
                 );
             } else {
                 children[0] =
                     Image::from_color(Self::ITEM_WIDTH, Self::ITEM_HEIGHT, 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() {

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

@@ -1,76 +1,164 @@
+use self::asset_item::AssetItem;
 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::{
     base::{panel::Panel, rect::Rect},
     layout::grid::{Grid, GridArrangeType},
+    traits::enter::Enter,
     widgets::{image::Image, widget_add_child},
 };
-use std::{cell::RefCell, fs, sync::Arc};
-
-use self::asset_item::AssetItem;
-use crate::starry_server::base::image::Image as ImageResource;
+use std::{collections::BTreeMap, fs, sync::Arc};
 
 pub mod asset_item;
 
 const DESKTOP_BG_PATH: &[u8] = include_bytes!("../resource/desktop_bg.png");
 
-pub struct AssetViewer {
+pub struct AssetManager {
     cur_path: String,
-    asset_grid: RefCell<Arc<Grid>>,
+    asset_grid: Arc<Grid>,
+    items: BTreeMap<(usize, usize), Arc<AssetItem>>,
+    panel: Panel,
 }
 
-impl AssetViewer {
+impl AssetManager {
     pub fn new() -> Self {
-        AssetViewer {
+        AssetManager {
             cur_path: String::from("/"),
-            asset_grid: RefCell::new(Grid::new()),
+            asset_grid: Grid::new(),
+            items: BTreeMap::new(),
+            panel: Panel::new(
+                Rect::new(0, 0, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32),
+                "Title",
+                Color::rgb(0, 0, 0),
+            ),
         }
     }
 
-    pub fn init(&self) {
-        let grid = self.asset_grid.borrow();
+    pub fn init(&mut self) {
+        let grid = self.asset_grid.clone();
         grid.set_upper_limit(5);
         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 == ' ' {
+                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(&"/");
+                    asset_manager.refresh();
+                }
+
+                return;
+            }
+
+            let row_offset: i32 = match char {
+                'a' => 0,
+                'A' => 0,
+                'd' => 0,
+                'D' => 0,
+                'w' => -1,
+                'W' => -1,
+                's' => 1,
+                'S' => 1,
+                _ => 0,
+            };
+
+            let col_offset: i32 = match char {
+                'a' => -1,
+                'A' => -1,
+                'd' => 1,
+                'D' => 1,
+                'w' => 0,
+                'W' => 0,
+                's' => 0,
+                'S' => 0,
+                _ => 0,
+            };
+
+            if row_offset == 0 && col_offset == 0 {
+                return;
+            }
+            let mut nxt_row = grid.focused_id.get().unwrap().0 as i32 + row_offset;
+            let mut nxt_col = grid.focused_id.get().unwrap().1 as i32 + col_offset;
+            loop {
+                if nxt_row < 0
+                    || nxt_row >= grid.max_row.get() as i32
+                    || nxt_col < 0
+                    || nxt_col >= grid.max_column.get() as i32
+                {
+                    return;
+                }
+
+                if grid
+                    .elements
+                    .borrow()
+                    .contains_key(&(nxt_row as usize, nxt_col as usize))
+                {
+                    break;
+                }
+
+                nxt_row += row_offset;
+                nxt_col += col_offset;
+            }
+
+            grid.focus(
+                grid.elements
+                    .borrow()
+                    .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;
+        });
+
+        self.panel.add_child(&Image::from_image(
+            ImageResource::from_path(DESKTOP_BG_PATH).unwrap(),
+        ));
+
+        self.panel.add_child(&(self.asset_grid));
     }
 
-    pub fn refresh(&self) {
+    pub fn refresh(&mut self) {
+        self.items.clear();
+        self.asset_grid.clear();
+
         // 读取目录中的文件列表
         if let Ok(entries) = fs::read_dir(&self.cur_path) {
             for entry in entries {
                 if let Ok(item) = entry {
-                    let item = AssetItem::new(
+                    let asset_item = AssetItem::new(
                         item.file_name().to_str().unwrap(),
                         item.metadata().unwrap().is_dir(),
                     );
-                    self.asset_grid.borrow_mut().add(&item);
-                    widget_add_child(self.asset_grid.borrow().clone(), item);
+                    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);
                 }
             }
+        } else {
+            println!(
+                "[Error] AssetManager failed to read dir {:?}",
+                self.cur_path
+            );
         }
 
-        // TODO 代码整理
-        let grid = self.asset_grid.borrow_mut();
-        let elements = grid.elements.borrow();
-        if let Some(widget) = elements.get(&(0, 0)) {
+        let grid = self.asset_grid.clone();
+        if let Some(widget) = grid.elements.borrow().get(&(0, 0)) {
             grid.focused_id.set(Some((0, 0)));
             grid.focus(widget);
         }
-    }
-
-    pub fn draw(&self) {
-        let panel = Panel::new(
-            Rect::new(0, 0, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32),
-            "Title",
-            Color::rgb(0, 0, 0),
-        );
 
-        panel.add_child(&Image::from_image(
-            ImageResource::from_path(DESKTOP_BG_PATH).unwrap(),
-        ));
+        self.panel.draw();
+    }
 
-        panel.add_child(&(self.asset_grid.borrow()));
-        panel.draw();
+    pub fn exec(&mut self) {
+        self.panel.exec();
     }
 }

+ 35 - 0
starry_applications/src/lib.rs

@@ -1,5 +1,40 @@
 extern crate starry_client;
 extern crate starry_server;
 extern crate starry_toolkit;
+extern crate termios;
 
 pub mod asset_manager;
+
+use std::io;
+use std::os::unix::io::AsRawFd;
+use termios::{tcsetattr, Termios};
+
+// TODO
+#[allow(dead_code)]
+pub fn set_terminal() -> 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);
+    new_settings.c_cc[termios::VMIN] = 1;
+    new_settings.c_cc[termios::VTIME] = 0;
+
+    tcsetattr(stdin, termios::TCSANOW, &new_settings)?;
+
+    Ok(())
+}
+
+// TODO
+#[allow(dead_code)]
+pub fn reset_terminal() -> 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)?;
+
+    Ok(())
+}

+ 6 - 5
starry_applications/src/main.rs

@@ -1,10 +1,11 @@
-use starry_apps::asset_manager::code::AssetViewer;
+use starry_apps::asset_manager::code::AssetManager;
 
 fn main() {
-    let viewer = AssetViewer::new();
+    // set_terminal();
+
+    // TODO
+    let mut viewer = AssetManager::new();
     viewer.init();
     viewer.refresh();
-    viewer.draw();
-
-    loop {}
+    viewer.exec();
 }

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

@@ -0,0 +1,35 @@
+use super::vector2::Vector2;
+
+#[derive(Copy, Clone, Debug)]
+pub enum Event {
+    Init,
+
+    Mouse {
+        point: Vector2,
+        left_button: bool,
+        middle_button: bool,
+        right_button: bool,
+    },
+
+    KeyPressed {
+        character: Option<char>,
+        scancode: u8,
+    },
+
+    KeyReleased {
+        character: Option<char>,
+        scancode: u8,
+    },
+
+    Scroll {
+        x: i32,
+        y: i32,
+    },
+
+    Resize {
+        width: u32,
+        height: u32,
+    },
+
+    Unknown,
+}

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

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

+ 72 - 1
starry_toolkit/src/base/panel.rs

@@ -1,5 +1,7 @@
 use std::{
     cell::{Cell, RefCell},
+    fs::File,
+    io::Read,
     sync::Arc,
 };
 
@@ -13,7 +15,9 @@ use starry_client::{
 
 use crate::{traits::focus::Focus, widgets::Widget};
 
-use super::rect::Rect;
+use super::{event::Event, rect::Rect};
+
+const TTY_DEVICE_PATH: &str = "/dev/char/tty0";
 
 /// 面板渲染器
 pub struct PanelRenderer<'a> {
@@ -74,6 +78,10 @@ pub struct Panel {
     pub running: Cell<bool>,
     /// 当前聚焦的窗口
     pub focused_widget: RefCell<Option<Arc<dyn Widget>>>,
+    /// 事件数组
+    events: RefCell<Vec<Event>>,
+    /// 需要重绘画面
+    redraw: bool,
 }
 
 impl Panel {
@@ -94,6 +102,8 @@ impl Panel {
             widgets: RefCell::new(Vec::new()),
             running: Cell::new(true),
             focused_widget: RefCell::new(None),
+            events: RefCell::new(Vec::new()),
+            redraw: true,
         }
     }
 
@@ -186,6 +196,67 @@ impl Panel {
             self.draw_widget(renderer, child);
         }
     }
+
+    pub fn tick(&mut self) {
+        // TODO 通过服务器,先从Window对象接收事件,再进行处理
+        self.handle_events();
+    }
+
+    /// 将事件传递给Widget对象
+    fn handle_events(&mut self) {
+        while let Some(event) = self.events.borrow_mut().pop() {
+            // 事件是否已被处理
+            let mut caught = 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 {
+                    break;
+                }
+            }
+        }
+    }
+
+    // TODO 临时函数 用于客户端直接处理用户输入
+    pub fn push_event(&self, event: Event) {
+        self.events.borrow_mut().push(event);
+    }
+
+    pub fn exec(&mut self) {
+        while self.running.get() {
+            self.polling_tty();
+            self.tick();
+            self.draw_if_needed();
+        }
+    }
+
+    /// 必要时重绘
+    fn draw_if_needed(&mut self) {
+        if self.redraw {
+            self.draw();
+            self.redraw = false;
+        }
+    }
+
+    // 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");
+        for i in 0..count {
+            self.push_event(Event::KeyPressed {
+                character: Some(bufffer[i] as char),
+                scancode: bufffer[i],
+            });
+        }
+    }
 }
 
 impl Focus for Panel {

+ 59 - 17
starry_toolkit/src/layout/grid.rs

@@ -7,8 +7,8 @@ use std::{
 use starry_client::base::renderer::Renderer;
 
 use crate::{
-    base::{rect::Rect, vector2::Vector2},
-    traits::focus::Focus,
+    base::{event::Event, rect::Rect, vector2::Vector2},
+    traits::{enter::Enter, focus::Focus},
     widgets::{PivotType, Widget},
 };
 
@@ -34,9 +34,13 @@ pub struct Grid {
     /// 每行/列的最大元素数
     upper_limit: Cell<usize>,
     /// 当前行数
-    current_row: Cell<usize>,
+    pub current_row: Cell<usize>,
     /// 当前列数
-    current_column: Cell<usize>,
+    pub current_column: Cell<usize>,
+    /// 当前最大行数
+    pub max_row: Cell<usize>,
+    /// 当前最大列数
+    pub max_column: Cell<usize>,
     /// 元素字典
     pub elements: RefCell<BTreeMap<(usize, usize), Arc<dyn Widget>>>,
     /// 当前选中的元素id(行列号)
@@ -45,6 +49,8 @@ pub struct Grid {
     pub focused_widget: RefCell<Option<Arc<dyn Widget>>>,
     /// 优先排列方式
     arrange_type: Cell<GridArrangeType>,
+    /// 键盘输入回调
+    enter_callback: RefCell<Option<Arc<dyn Fn(&Self, char, &mut bool)>>>,
 }
 
 impl Grid {
@@ -60,10 +66,13 @@ impl Grid {
             upper_limit: Cell::new(0),
             current_row: Cell::new(0),
             current_column: Cell::new(0),
+            max_row: Cell::new(0),
+            max_column: Cell::new(0),
             elements: RefCell::new(BTreeMap::new()),
             focused_id: Cell::new(None),
             focused_widget: RefCell::new(None),
             arrange_type: Cell::new(GridArrangeType::Vertical),
+            enter_callback: RefCell::new(None),
         })
     }
 
@@ -78,14 +87,16 @@ impl Grid {
         self
     }
 
-    pub fn add<T: Widget>(&self, element: &Arc<T>) {
+    pub fn add<T: Widget>(&self, element: &Arc<T>) -> (usize, usize) {
         self.find_next_slot();
         self.elements.borrow_mut().insert(
             (self.current_row.get(), self.current_column.get()),
             element.clone(),
         );
+        let res = (self.current_row.get(), self.current_column.get());
         self.move_index();
         self.arrange_elements(false);
+        return res;
     }
 
     /// 找到下一个可放置元素的位置
@@ -125,10 +136,6 @@ impl Grid {
         self.arrange_elements(false);
     }
 
-    pub fn clear(&self) {
-        self.elements.borrow_mut().clear();
-    }
-
     pub fn remove(&self, column: usize, row: usize) {
         self.elements.borrow_mut().remove(&(row, column));
     }
@@ -171,6 +178,9 @@ impl Grid {
             }
         }
 
+        self.max_row.set(rows.len());
+        self.max_column.set(cols.len());
+
         let space_x = self.space_x.get();
         let space_y = self.space_y.get();
 
@@ -200,6 +210,13 @@ impl Grid {
             child.arrange_all();
         }
     }
+
+    pub fn clear(&self) {
+        self.children.borrow_mut().clear();
+        self.elements.borrow_mut().clear();
+        self.current_column.set(0);
+        self.current_row.set(0);
+    }
 }
 
 impl Widget for Grid {
@@ -228,18 +245,31 @@ impl Widget for Grid {
     }
 
     fn draw(&self, renderer: &mut dyn Renderer, _focused: bool) {
-        fn draw_widget(widget: &Arc<dyn Widget>, renderer: &mut dyn Renderer, focused: bool) {
+        for (&(_row, _col), widget) in self.elements.borrow().iter() {
             widget.update();
-            widget.draw(renderer, focused);
-
-            for child in widget.children().borrow().iter() {
-                draw_widget(child, renderer, focused);
-            }
+            widget.draw(renderer, self.is_focused(widget));
         }
+    }
+
+    fn handle_event(
+        &self,
+        event: Event,
+        _focused: bool,
+        redraw: &mut bool,
+        caught: &mut bool,
+    ) -> bool {
+        match event {
+            Event::KeyPressed { character, .. } => {
+                if let Some(character) = character {
+                    self.emit_enter(character, redraw);
+                }
 
-        for (&(_col, _row), widget) in self.elements.borrow().iter() {
-            draw_widget(widget, renderer, self.is_focused(widget));
+                *caught = true;
+            }
+            // TODO
+            _ => {}
         }
+        false
     }
 }
 
@@ -252,3 +282,15 @@ impl Focus for Grid {
         (*self.focused_widget.borrow_mut()) = Some(widget.clone());
     }
 }
+
+impl Enter for Grid {
+    fn emit_enter(&self, char: char, redraw: &mut 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) {
+        (*self.enter_callback.borrow_mut()) = Some(Arc::new(func));
+    }
+}

+ 6 - 0
starry_toolkit/src/traits/enter.rs

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

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

@@ -1,3 +1,4 @@
+pub mod enter;
 pub mod focus;
 pub mod text;
 pub mod transform;

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

@@ -7,7 +7,7 @@ use std::{
 use starry_client::base::renderer::Renderer;
 use starry_server::core::{SCREEN_HEIGHT, SCREEN_WIDTH};
 
-use crate::base::{rect::Rect, vector2::Vector2};
+use crate::base::{event::Event, rect::Rect, vector2::Vector2};
 
 pub mod image;
 pub mod label;
@@ -124,6 +124,17 @@ pub trait Widget: Any {
     /// 更新组件状态
     fn update(&self) {}
 
+    /// 处理输入事件
+    fn handle_event(
+        &self,
+        _event: Event,
+        _focused: bool,
+        _redraw: &mut bool,
+        _caught: &mut bool,
+    ) -> bool {
+        false
+    }
+
     fn set_pivot_type(&self, pivot_type: PivotType) {
         self.set_pivot_type_base(pivot_type);
     }