🎮

[Rustのデザインパターン 学習] Command [振る舞い]

2023/02/12に公開

https://rust-unofficial.github.io/patterns/patterns/behavioural/command.html

はじめに

自分の努力不足なのですが残念ながら、2/5(日)は記事投稿できませんでした。

背景

Commandパターンの学習で、「せっかくならGUI上で何かしらコマンドを実行されるよう実装したい!」と思い立ったのが事の始まりです。

  • RustでGUIフレームワークというとTauriを以前から覚えようとしていたのですが、
    JavaScript, TypeScriptやフロントエンドのフレームワークに疎くなかなか理解が進まない状態でした。
  • その折、以下Dioxusをおすすめされていたやりとりを見かけ、早速触ってみることにしました。

https://twitter.com/monamour555/status/1617276204398301187?s=20

状況

...ドキュメントを読み進め、ガイド通りにコードを模写し少しずつ理解が進んでいたのですが...

use_state()で仕様変更があり、公式のコード通り動かないという事態に直面します。

どうにもわからんというところで、グーグル先生にヘルプを求めたところ、
同じくZennで記事を投稿されていた方のおかげで問題が解決することとなりました。
本当にありがとうございます...。

というところで本編に戻ります。

https://zenn.dev/j1ngzoue/articles/901b3cd40fec66

内容

動作をgif動画にしました。

  • ボタンを押すと、計算結果を返します
  • 操作履歴をqueueにして実行できるよう実装しました。



use chrono::prelude::*;
use dioxus::prelude::*;

fn main() {
    dioxus::web::launch(app);
}
static LABEL1: &str = "DoubleUp";
static LABEL2: &str = "Decrease";
static LABEL3: &str = "Execute";
static LABEL4: &str = "Rollback";

type TCommand = im_rc::Vector::<i128>;
type THistory = im_rc::Vector::<(String, i128, String)>;

fn app(cx: Scope) -> Element {
    let now = Local::now().format("%y/%m/%d %H:%M:%S%.3f").to_string();
    
    //HACK:nowのみを参照する場合、各ボタン内でドロップされるため複製している。他に解決法があるはず!
    let now2 = now.clone();
    let now3 = now.clone();
    let now4 = now.clone();
    let now5 = now.clone();
    
    let (sum, set_sum)           = use_state(&cx, || 1 as i128);
    let (_e, set_execute)        = use_state(&cx, || TCommand::new());
    let (_r, set_rollback)       = use_state(&cx, || TCommand::new());
    let (histories, set_history) = use_state(&cx, || THistory::new());
    
    let histories_list = histories.iter().map(|t| {
        rsx!(
            li {"[{t.0}][{t.2}]:\t {t.1}\t"}
        )
    });

    cx.render(rsx! {

        h1 { "Sum: {sum}" }
        
        button {onclick:move |_| {
            calc_command(set_sum, *sum, |i| i*2, set_rollback, set_history, now2.clone(), LABEL1);
            },
            "x2"
        }

        button { onclick: move |_| {
            calc_command(set_sum, *sum, |i|i-1, set_rollback, set_history, now3.clone(), LABEL2);
        }, "-1" }

        //TODO:要素0の場合はボタンを無効化
        button { onclick: move |_| {
            if let Some(i) = set_execute.make_mut().pop_back() {
                set_rollback.make_mut().push_back(*sum);
                set_sum(i);
                set_history.make_mut().push_back((now4.clone(),i, format!("{:*^10}",LABEL3)));
            }
        }, "Execute" }

        //TODO:要素0の場合はボタンを無効化
        button { onclick: move |_| {
            if let Some(i) = set_rollback.make_mut().pop_back() {
                set_execute.make_mut().push_back(*sum);
                set_sum(i);
                set_history.make_mut().push_back((now5.clone(),i, format!("{:*^10}",LABEL4)));
            }
        }, "Rollback"}
        ul {
            histories_list
        }
    })
}


//HACK:引数が多すぎるのでstructにまとめる
/// f(x)にてsumを更新 
fn calc_command<'a>(set_sum:&'a UseState<i128>, sum:i128, f:impl FnOnce(i128)->i128, schema: &'a UseState<TCommand>, 
                    history:&'a UseState<THistory>, now:String, command_type:&str) 
{   
    schema.make_mut().push_back(sum);   
    let num_update = f(sum);
    history.make_mut().push_back((now,num_update,format!("{:*^10}",command_type)));
    set_sum(num_update);        
}

Cargo.toml
[package]
name = "demo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
dioxus = { version = "0.1.0", features = ["web"] }
chrono = "0.4"
im-rc = "15.0.0"

謝辞

読んで頂きありがとうございました。
次週もがんばっていきます。

Discussion