From 15c7dc765444887841524773cb7ffef24891e6c9 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Wed, 2 Mar 2022 15:18:10 +0900 Subject: [PATCH] Reorganize front-end source into modules --- front_end/src/constants.rs | 5 + front_end/src/main.rs | 368 +------------------------------- front_end/src/state.rs | 124 +++++++++++ front_end/src/yew_components.rs | 236 ++++++++++++++++++++ 4 files changed, 372 insertions(+), 361 deletions(-) create mode 100644 front_end/src/constants.rs create mode 100644 front_end/src/state.rs create mode 100644 front_end/src/yew_components.rs diff --git a/front_end/src/constants.rs b/front_end/src/constants.rs new file mode 100644 index 0000000..090bdbd --- /dev/null +++ b/front_end/src/constants.rs @@ -0,0 +1,5 @@ +//const ROWS: u8 = 8; +pub const COLS: u8 = 7; + +pub const INFO_TEXT_MAX_ITEMS: u32 = 100; +pub const INFO_TEXT_HEIGHT: i32 = 400; diff --git a/front_end/src/main.rs b/front_end/src/main.rs index bee0e46..ae73e13 100644 --- a/front_end/src/main.rs +++ b/front_end/src/main.rs @@ -1,367 +1,13 @@ -use std::cell::{Cell, RefCell}; -use std::collections::VecDeque; -use std::rc::Rc; +mod state; +mod yew_components; +mod constants; + +use state::SharedState; +use yew_components::Wrapper; use yew::prelude::*; -//const ROWS: u8 = 8; -const COLS: u8 = 7; - -const INFO_TEXT_MAX_ITEMS: u32 = 100; -const INFO_TEXT_HEIGHT: i32 = 400; - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -enum BoardState { - Empty, - Cyan, - Magenta, -} - -impl Default for BoardState { - fn default() -> Self { - Self::Empty - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -struct MessageBus { - queued: VecDeque, -} - -impl Default for MessageBus { - fn default() -> Self { - Self { - queued: VecDeque::new(), - } - } -} - -impl MessageBus { - pub fn get_next_msg(&mut self) -> Option { - self.queued.pop_front() - } - - pub fn push_msg(&mut self, msg: String) -> Result<(), String> { - self.queued.push_back(msg); - Ok(()) - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -enum Turn { - CyanPlayer, - MagentaPlayer, -} - -#[derive(Clone, Debug, PartialEq)] -struct SharedState { - board: [Rc>; 56], - bus: Rc>, - turn: Turn, - info_text_ref: NodeRef, -} - -impl Default for SharedState { - fn default() -> Self { - Self { - // cannot use [; 56] because Rc does not impl Copy - board: [ - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - Rc::new(Cell::new(BoardState::default())), - ], - bus: Rc::new(RefCell::new(MessageBus::default())), - turn: Turn::CyanPlayer, - info_text_ref: NodeRef::default(), - } - } -} - -struct Slot {} - -enum SlotMessage { - Press(u8), -} - -#[derive(Clone, PartialEq, Properties)] -struct SlotProperties { - idx: u8, - state: Rc>, - bus: Rc>, -} - -impl Component for Slot { - type Message = SlotMessage; - type Properties = SlotProperties; - - fn create(_ctx: &Context) -> Self { - Self {} - } - - fn view(&self, ctx: &Context) -> Html { - let idx = ctx.props().idx; - let state = match ctx.props().state.as_ref().get() { - BoardState::Empty => "open", - BoardState::Cyan => "cyan", - BoardState::Magenta => "magenta", - }; - let idx_copy = idx; - let onclick = ctx.link().callback(move |_| SlotMessage::Press(idx_copy)); - let col = idx % COLS; - let row = idx / COLS; - html! { - - } - } - - fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { - match msg { - SlotMessage::Press(idx) => { - let (shared, _) = ctx - .link() - .context::(Callback::noop()) - .expect("shared to be set"); - - let result = shared.bus.borrow_mut().push_msg(format!("pressed {idx}")); - if let Err(e) = result { - log::error!("Error pushing msg to bus: {}", e); - } else { - // DEBUG - //log::info!("Pushed \"pressed {idx}\" msg to bus"); - } - } - } - - // notify Wrapper with message - if let Some(p) = ctx.link().get_parent() { - p.clone().downcast::().send_message(()); - } - - true - } -} - -struct Wrapper {} - -impl Component for Wrapper { - type Message = (); - type Properties = (); - - fn create(_ctx: &Context) -> Self { - Self {} - } - - fn view(&self, ctx: &Context) -> Html { - let (shared, _) = ctx - .link() - .context::(Callback::noop()) - .expect("state to be set"); - html! { -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
// wrapper - } - } - - fn rendered(&mut self, ctx: &Context, _first_render: bool) { - let (shared, _) = ctx - .link() - .context::(Callback::noop()) - .expect("state to be set"); - - loop { - if let Some(msg) = shared.bus.borrow_mut().get_next_msg() { - let split_str: Vec<&str> = msg.split_whitespace().collect(); - if split_str.len() == 2 { - if split_str[0] == "pressed" { - if let Ok(idx) = split_str[1].parse::() { - let output_str: String = format!("Got {idx} pressed."); - // DEBUG - //log::info!("{}", &output_str); - if let Some(info_text) = - shared.info_text_ref.cast::() - { - // create the new text to be appended in the output - let window = web_sys::window().expect("no window exists"); - let document = - window.document().expect("window should have a document"); - let p = document - .create_element("p") - .expect("document should be able to create

"); - p.set_text_content(Some(&output_str)); - - // check if scrolled to bottom - let at_bottom: bool = info_text.scroll_top() + INFO_TEXT_HEIGHT - >= info_text.scroll_height(); - - // append text to output - info_text - .append_with_node_1(&p) - .expect("should be able to append to info_text"); - while info_text.child_element_count() > INFO_TEXT_MAX_ITEMS { - info_text - .remove_child(&info_text.first_child().unwrap()) - .expect("should be able to limit items in info_text"); - } - - // scroll to bottom only if at bottom - if at_bottom { - info_text.set_scroll_top(info_text.scroll_height()); - } - } else { - log::warn!("Failed to get \"info_text\""); - } - } - } - } - } else { - break; - } - } - } - - fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { - true - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -struct InfoText {} - -impl Component for InfoText { - type Message = (); - type Properties = (); - - fn create(_ctx: &Context) -> Self { - Self {} - } - - fn view(&self, ctx: &Context) -> Html { - let (shared, _) = ctx - .link() - .context::(Callback::noop()) - .expect("state to be set"); - html! { -

- {"Hello"} -
- } - } -} - #[function_component(App)] -fn app() -> Html { +pub fn app() -> Html { let ctx = use_state(SharedState::default); html! { context={(*ctx).clone()}> diff --git a/front_end/src/state.rs b/front_end/src/state.rs new file mode 100644 index 0000000..0342f44 --- /dev/null +++ b/front_end/src/state.rs @@ -0,0 +1,124 @@ +use std::collections::VecDeque; +use std::cell::{Cell, RefCell}; +use std::rc::Rc; +use yew::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MessageBus { + queued: VecDeque, +} + +impl MessageBus { + pub fn get_next_msg(&mut self) -> Option { + self.queued.pop_front() + } + + pub fn push_msg(&mut self, msg: String) -> Result<(), String> { + self.queued.push_back(msg); + Ok(()) + } +} + +impl Default for MessageBus { + fn default() -> Self { + Self { + queued: VecDeque::new(), + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum BoardState { + Empty, + Cyan, + Magenta, +} + +impl Default for BoardState { + fn default() -> Self { + Self::Empty + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Turn { + CyanPlayer, + MagentaPlayer, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SharedState { + pub board: [Rc>; 56], + pub bus: Rc>, + pub turn: Turn, + pub info_text_ref: NodeRef, +} + +impl Default for SharedState { + fn default() -> Self { + Self { + // cannot use [; 56] because Rc does not impl Copy + board: [ + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + Rc::new(Cell::new(BoardState::default())), + ], + bus: Rc::new(RefCell::new(MessageBus::default())), + turn: Turn::CyanPlayer, + info_text_ref: NodeRef::default(), + } + } +} diff --git a/front_end/src/yew_components.rs b/front_end/src/yew_components.rs new file mode 100644 index 0000000..34ddb5a --- /dev/null +++ b/front_end/src/yew_components.rs @@ -0,0 +1,236 @@ +use crate::state::{MessageBus, BoardState, Turn, SharedState}; +use crate::constants::{COLS, INFO_TEXT_HEIGHT, INFO_TEXT_MAX_ITEMS}; +use yew::prelude::*; +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +pub struct Slot {} + +pub enum SlotMessage { + Press(u8), +} + +#[derive(Clone, PartialEq, Properties)] +pub struct SlotProperties { + idx: u8, + state: Rc>, + bus: Rc>, +} + +impl Component for Slot { + type Message = SlotMessage; + type Properties = SlotProperties; + + fn create(_ctx: &Context) -> Self { + Self {} + } + + fn view(&self, ctx: &Context) -> Html { + let idx = ctx.props().idx; + let state = match ctx.props().state.as_ref().get() { + BoardState::Empty => "open", + BoardState::Cyan => "cyan", + BoardState::Magenta => "magenta", + }; + let idx_copy = idx; + let onclick = ctx.link().callback(move |_| SlotMessage::Press(idx_copy)); + let col = idx % COLS; + let row = idx / COLS; + html! { + + } + } + + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + match msg { + SlotMessage::Press(idx) => { + let (shared, _) = ctx + .link() + .context::(Callback::noop()) + .expect("shared to be set"); + + let result = shared.bus.borrow_mut().push_msg(format!("pressed {idx}")); + if let Err(e) = result { + log::error!("Error pushing msg to bus: {}", e); + } else { + // DEBUG + //log::info!("Pushed \"pressed {idx}\" msg to bus"); + } + } + } + + // notify Wrapper with message + if let Some(p) = ctx.link().get_parent() { + p.clone().downcast::().send_message(()); + } + + true + } +} + +pub struct Wrapper {} + +impl Component for Wrapper { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self {} + } + + fn view(&self, ctx: &Context) -> Html { + let (shared, _) = ctx + .link() + .context::(Callback::noop()) + .expect("state to be set"); + html! { +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
// wrapper + } + } + + fn rendered(&mut self, ctx: &Context, _first_render: bool) { + let (shared, _) = ctx + .link() + .context::(Callback::noop()) + .expect("state to be set"); + + loop { + if let Some(msg) = shared.bus.borrow_mut().get_next_msg() { + let split_str: Vec<&str> = msg.split_whitespace().collect(); + if split_str.len() == 2 { + if split_str[0] == "pressed" { + if let Ok(idx) = split_str[1].parse::() { + let output_str: String = format!("Got {idx} pressed."); + // DEBUG + //log::info!("{}", &output_str); + if let Some(info_text) = + shared.info_text_ref.cast::() + { + // create the new text to be appended in the output + let window = web_sys::window().expect("no window exists"); + let document = + window.document().expect("window should have a document"); + let p = document + .create_element("p") + .expect("document should be able to create

"); + p.set_text_content(Some(&output_str)); + + // check if scrolled to bottom + let at_bottom: bool = info_text.scroll_top() + INFO_TEXT_HEIGHT + >= info_text.scroll_height(); + + // append text to output + info_text + .append_with_node_1(&p) + .expect("should be able to append to info_text"); + while info_text.child_element_count() > INFO_TEXT_MAX_ITEMS { + info_text + .remove_child(&info_text.first_child().unwrap()) + .expect("should be able to limit items in info_text"); + } + + // scroll to bottom only if at bottom + if at_bottom { + info_text.set_scroll_top(info_text.scroll_height()); + } + } else { + log::warn!("Failed to get \"info_text\""); + } + } + } + } + } else { + break; + } + } + } + + fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { + true + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct InfoText {} + +impl Component for InfoText { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self {} + } + + fn view(&self, ctx: &Context) -> Html { + let (shared, _) = ctx + .link() + .context::(Callback::noop()) + .expect("state to be set"); + html! { +

+ {"Hello"} +
+ } + } +}