From bc9dd3d06f23e2592765e134d3e631e116e87031 Mon Sep 17 00:00:00 2001 From: "Artem V. Ageev" Date: Sat, 8 Jun 2024 19:54:52 +0300 Subject: [PATCH] add checkbuttons' example (#10) * add checkbuttons' example * add inputchoice's example * add progress's example * add progress's example * add checkbuttons' example * add checkbuttons' example * fmt * fix flerrands * add temperature * add csv's demo * add csv's demo * add flightbooker * add crud * add InputChoice to FlResters * add flglyph * add flglyph --- README.md | 45 +++- assets/counter.png | Bin 0 -> 2654 bytes assets/crud.png | Bin 0 -> 8928 bytes assets/flglyph.png | Bin 0 -> 10230 bytes assets/flnetport.gif | Bin 14159 -> 0 bytes assets/flnetport.png | Bin 0 -> 6661 bytes assets/flresters.png | Bin 0 -> 27128 bytes assets/temperature.png | Bin 0 -> 4602 bytes demos/Cargo.toml | 4 +- demos/cairo/assets/flcairo.png | Bin 0 -> 5271 bytes demos/cairo/src/main.rs | 206 ++++++++------- demos/cairo/src/model/mod.rs | 18 ++ demos/csv/Cargo.toml | 13 + demos/csv/assets/flcsv.png | Bin 0 -> 4721 bytes demos/{flerrands => csv}/assets/flerrands.gif | Bin demos/csv/assets/historical_data/GME.csv | 152 +++++++++++ demos/csv/assets/historical_data/dlpn.csv | 55 ++++ demos/csv/assets/historical_data/oil.csv | 82 ++++++ demos/csv/src/main.rs | 138 ++++++++++ demos/csv/src/model/mod.rs | 63 +++++ demos/flightbooker/Cargo.toml | 10 + demos/flightbooker/assets/flightbooker.png | Bin 0 -> 8593 bytes demos/flightbooker/src/main.rs | 126 ++++++++++ demos/flightbooker/src/model/mod.rs | 57 +++++ demos/{flerrands => fltodo}/Cargo.toml | 0 demos/fltodo/assets/fltodo.gif | Bin 0 -> 15610 bytes demos/{flerrands => fltodo}/src/main.rs | 77 +++--- demos/fltodo/src/model/mod.rs | 30 +++ examples/checkbutton.rs | 84 +++++++ examples/counter.rs | 89 +++---- examples/crud.rs | 236 ++++++++++++++++++ examples/flcalculator.rs | 118 +++++---- examples/fldialect.rs | 192 +++++--------- examples/flglyph.rs | 139 +++++++++++ examples/flnetport.rs | 90 +++---- examples/flpicture.rs | 107 +++----- examples/flresters.rs | 18 +- examples/input.rs | 56 ----- examples/inputchoice.rs | 111 ++++++++ examples/menu.rs | 8 +- examples/progress.rs | 79 ++++++ examples/temperature.rs | 94 +++++++ 42 files changed, 1948 insertions(+), 549 deletions(-) create mode 100644 assets/counter.png create mode 100644 assets/crud.png create mode 100644 assets/flglyph.png delete mode 100644 assets/flnetport.gif create mode 100644 assets/flnetport.png create mode 100644 assets/flresters.png create mode 100644 assets/temperature.png create mode 100644 demos/cairo/assets/flcairo.png create mode 100644 demos/cairo/src/model/mod.rs create mode 100644 demos/csv/Cargo.toml create mode 100644 demos/csv/assets/flcsv.png rename demos/{flerrands => csv}/assets/flerrands.gif (100%) create mode 100644 demos/csv/assets/historical_data/GME.csv create mode 100644 demos/csv/assets/historical_data/dlpn.csv create mode 100644 demos/csv/assets/historical_data/oil.csv create mode 100644 demos/csv/src/main.rs create mode 100644 demos/csv/src/model/mod.rs create mode 100644 demos/flightbooker/Cargo.toml create mode 100644 demos/flightbooker/assets/flightbooker.png create mode 100644 demos/flightbooker/src/main.rs create mode 100644 demos/flightbooker/src/model/mod.rs rename demos/{flerrands => fltodo}/Cargo.toml (100%) create mode 100644 demos/fltodo/assets/fltodo.gif rename demos/{flerrands => fltodo}/src/main.rs (69%) create mode 100644 demos/fltodo/src/model/mod.rs create mode 100644 examples/checkbutton.rs create mode 100644 examples/crud.rs create mode 100644 examples/flglyph.rs delete mode 100644 examples/input.rs create mode 100644 examples/inputchoice.rs create mode 100644 examples/progress.rs create mode 100644 examples/temperature.rs diff --git a/README.md b/README.md index 4648d98..9558916 100644 --- a/README.md +++ b/README.md @@ -74,13 +74,30 @@ impl Sandbox for Counter { To run the [examples:](/examples) ```bash +cargo run --example counter +cargo run --example temperature +cargo run --example crud cargo run --example flcalculator cargo run --example fldialect +cargo run --example flglyph cargo run --example flnetport cargo run --example flpicture +cargo run --example flresters ... ``` +### [FlCounter](/examples/counter.rs) + +![FlCalculator](/assets/counter.png) + +### [FlTemperature](/examples/temperature.rs) + +![FlTemperature](/assets/temperature.png) + +### [FlCRUD](/examples/crud.rs) + +![FlCRUD](/assets/crud.png) + ### [FlCalculator](/examples/flcalculator.rs) ![FlCalculator](/assets/flcalculator.gif) @@ -89,16 +106,36 @@ cargo run --example flpicture ![FlDialect](/assets/fldialect.gif) +### [FlGlyph](/examples/flglyph.rs) + +![FlGlyph](/assets/flglyph.png) + ### [FlNetPort](/examples/flnetport.rs) -![FlNetPort](/assets/flnetport.gif) +![FlNetPort](/assets/flnetport.png) ### [FlPicture](/examples/flpicture.rs) ![FlPicture](/assets/flpicture.gif) -## Demo +### [FlResters](/examples/flresters.rs) + +![FlResters](/assets/flresters.png) + +## Demos + +### [FlTodo](/demos/fltodo) + +![FlTodo](/demos/fltodo/assets/fltodo.gif) + +### [FlCSV](/demos/csv) + +![FlCSV](/demos/csv/assets/flcsv.png) + +### [FlCairo](/demos/cairo) + +![FlCairo](/demos/cairo/assets/flcairo.png) -### [FlErrands](/demos/flerrands) +### [Flightbooker](/demos/flightbooker) -![FlPicture](/demos/flerrands/assets/flerrands.gif) +![Flightbooker](/demos/flightbooker/assets/flightbooker.png) diff --git a/assets/counter.png b/assets/counter.png new file mode 100644 index 0000000000000000000000000000000000000000..b7f79ce7fb322f44c7869a14ef68bd117c374597 GIT binary patch literal 2654 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV9elP0*ZJ9++_n&%*9TgAsieWw;%dHU|`@9 z^K@|xshIQjwxjpd+f2tk?#W@5HQ_RQc|g}s%JuRBPZO?L<`WL+KV1Hy_>1T)lM{)} z3Ia_IF3n}Bf|3{2LSFHRbU{r7z#iLY;3;#&kU5Zk?Y^SZJU{yS1(!2RhIhkCfq91n7;gzBXtgXi zaB0{!bd@Yx{o%}+GfV2WDqh<8;6B@*be0)ib%%_tt#@zPGUeCXSYE+@S(%xcWo2fy zTtCb?3`FO)q#H=AS+hn*M`sD&h4TUrW4O7wwY9aI4=U6O{D@`papixg`Bj#${h)D) zk;{%F1r}ElmUS=$3-bG~eE;gzt52VbK1kT|Obq_~`LlW6ogJmGuVt8gdh_|pr%#_= zy;{X)tP6BOPKDoVYpLF*<1Z3B=1BFr@ypxAL`Lqc`ub|Fb-A2P#e##&-4;&-s{PX& zv~lm=xmgAFUwio{Zr)e-_t%q?ll$fEr?JoPm$TgkO1R$ioi{ z%F4d|{r&xAbII}Si(mX3R(7&v5~-CrWDw(j3w zUs7Ts*MFJ4@iV7^=)@0m=FEBX_U+qu@4j7ru;HTX`n;rAZ z9q;ch&$o~P1<|*+x1Xxt-&gzk6`QSh?MyLXq?y^IOV_-;we^bOu3gM^zrVeGet!P> ze@}k=uqdCiZ-sua3((i?&$k@BzrUVeZt;BU2jT1EmiAv*X`X+t=Eny|pds@ZgD)P= zp1t%SJ3Bit?t|j>3JVGr?DG+1T()TPhbe6A_8)E?7I-K$`J_wjOg*5L_75%X?D*=I zZJw-n$o1Icjp+}4#DQv8e_*lyd<&GY?D)@K5d>wi7g-$)|JjFbCL^NQSk!VnykT3~ zwd>a}r!NTZ?(S}HZ=ZZprFKGJ&+*4=*RJjDBK bXs~CH=?l3fdgbCA1|aZs^>bP0l+XkKh;Ec9 literal 0 HcmV?d00001 diff --git a/assets/crud.png b/assets/crud.png new file mode 100644 index 0000000000000000000000000000000000000000..66daa4ec06ca865e6d8508f7e40e01803bf5d2a5 GIT binary patch literal 8928 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 Oj62F&O8A@Rzx@{`P7xje literal 0 HcmV?d00001 diff --git a/assets/flglyph.png b/assets/flglyph.png new file mode 100644 index 0000000000000000000000000000000000000000..6509b7309c77c2fa45068e2bd8287b4ebf2cfda2 GIT binary patch literal 10230 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 diff --git a/assets/flnetport.png b/assets/flnetport.png new file mode 100644 index 0000000000000000000000000000000000000000..b53b0df6bf8bc168f9cbb83442624ebcdf8fb98a GIT binary patch literal 6661 zcmeHMc{J2}{~zs(^4uaKbaO)?TNxV6y|*OE9Bi{=Q#wnzj*zg2Gh>}S_N!6tzWou{%do>!eRF#K)LlYH~%XAha1%stboW|W#+vYt4n zTReX)ckM;h?M$m_%=f>`EA2fe@b=J~{c)ls#IB|$g&f|fflO;tB6HPmWLYbLPC_nE zx^1$vk>1+I12x{59f-EFm5(R{LP$nnS0rMG7-HLBZXovj7%6ZZ@n1hgwtYk`x>Ves z{cw9OhG@gLxt`wwv{@^Z1=l?K%l1%7x4E5X<;8v80 z!(CW-Yp+UNn7tS_8i%4yfC%Xy=D-=hhI-Y#dOLrwLay)rl9 zk6n4FnPV!59mn4w{t}7U{^r-`1txB1cZwlopO=)}6_-$agHZY2#>Qr1Vxp^OgeWqS zo0|(~p)Xh>R8VP3b{Y-`a@z3Ud-hkHl#!8%&F$#u*x1;Zot<4H&$isEAP^Q9J;i+l z41RD}@%pP9%aiSL##yz5;>pCfZ{I>tO|}9)b#>vxH>UIR^I>N9+XoLGBuF49r>1JE ztFzDhpU${?$!w+68>=8E=eG9x@x`%sLg=iqgI}kn0s{k0n3wWB&d#6G(ZP1*8@+h( zVs36u&LroUxOnC>l$y9q+g7L84ANcpYO8y^ijq3xOSK0LkCvt#!hFl|Yt?L(zBkib z8mDYucmH-Q)gxuE&xzz9Mqz>1P+d=Vx3Hd>v9YngKa1!$H9t(Q4`PlHuyY>g&Yf#c zl*E1T!XCA>wsyO9D{QGn&VrQ9;c%omBfFE(+%+_pnbf^f?mOPphlwCRdp6ozYKID( z=S(80>o_{c)!kiQPEJl)S-E;H^PERrU0uG^NV9Z=wv0?(e!lCc8Wo2;Uz6$SY4m!F z$q~itrqNp+hrpA#^2w6rjI}kVcqfY@VGNt?%aIn@kL?1)YMd( z_HtW>x?4zSXpDDib~gFu`&f91`T6ug{D?@HMP^HPmjsj$Vkmyw-yl*Nli=Z z?dh?+c1>7V_<7C};d*&)ZLMbVWqVSPs{iKu_wSkdmjeOFL&vj^Xoz z#FhkCI0l53Io>Q+KIcOu64AVMT9PD6<0kU_`SanM19V49n3itH8jBD?4q-9&i=N>u z<9Sz0Q!+Bvi!oaa3~!@PnEkz#gM*}A=U3OXY4L<|ZD!-)I8kNpgmOgx(2#?v>0%s) za}!XZzQK6ZDdu%1sp&fPCv~^ed8&!!uH8XtGjns~Am&Zdt;VpR)!F`1yBb$lS9tkz z8CP?3LN#J@ePwI@4CKPQtjdbY%Jef1b$9zK%B`NJ?K0uXIh{iy3tqesIdUX%&mzpI ztFJ%&xDgQ6S8B&z9q?|Zb;GhLINZSCAnD%3hn$?8?u~K#>KhGwfTyLVy12NUJoI?2 z0nHxtgDc*>ecJ|ZJK2t-hi}d`G&B?y70Kblmd4)4Wn^SXAictPTWmW!y9J^Ti_rtQ zLk*a-4GrDg()}7_oU!zWo|!OfWregpiAz_;001Mj!`6F>tu4~M>6D?aE(=prm74>E zunC3j+qZXVE*pEfW;At*8CqKQz`Kv7a{-3NcwI7pHbYm(>1;HvGEcSF`(u3Ovt7;6 zw3l@5YK@_xAucm>-@bi0A&kO6I<_V+&$%(|DGrCjmrKzdmdx+(VzF38@O?eK_qlp;s|$3rrsmU}94@1nU}b5EUF~-R zL;|&lG4F5Y><4g%hJ*l?=Gjc+&8$P{iK~W&5*|^O#a1qEZb?C&N=izeePs?Y2PHFy z<+yBAW!qapi3m0=8q}K(O-;?~*8x60DwNfn{cu}ZX3KP?M#1y1U%!U&Ht9Sn zZ>3#9RA($r zv=Z!zPc}$nG&C=4YlFpXn18hH^Xpz?FXwh8XC*m^cTaDxmAUz+-rfM}fU#YqsN(gH zeSLjBJuEhrhh4EKJ!zOW3xru*TwGbH?&ZkgobB+rQ$0#hPx7-A)`OQU3}{ktb9K$% zhlLG1`pWdo?vFJ-226YRS4_0X;kCm#Yb_~q&9<6*4b#tLPiUP~P?*t2Z1&r($a!mD*gH{IsZ!35O*@Vbyn7W zB|a06M~7XBH_XD}P$9Zmze0NZP9D4IM$3^rb}Xy0xS;Fkh>U)sAa7LcSzX-*gkral zRJ>4BUy03O<%Q|#mW~eNQVmYVrlxg5@4!GPdw%Fb%)tao(KG&VT3?!|5111d6MIB| z;zNsK7ehe;KJRr+D=8_F<}SuKI5^vtvq$*gKao$ODRW1ZNSXeAa`F*&X zcnR{`*VmWPIyN@evbAE82v1X%aOwKtqX~P(b@Eo>!H5a(n<=o+IKCk3(LXNGk#*b4 zD`!P(#Af4oB5c;(Dq3t+dPT|G(^E-7!7%DIi4=CiIw1gdt*EG2U!E#RYm7K4Ngb5b zGqbR;fPCS|gwmpbilG#+Lx*3&v7?8BRexq6N86F~V_VtbkYY*F=#mCSb{2#I`7OSVHXXnnH@Ss3~ zh0Q*rZ=--=DN7#|+S}Xp^ztHMGK^&pg_iaA_h(%GAt!>n4$lgqRK`%k zvRXV#h%Q-LQ%?MZg4H%y@em-loM2a52C4-iDk74Nj?j(G1wyT^sbR0K_I7u#u-QqK zEsqb&m0_rk_V$B=gZ3B<)Od#GONhe)Y1ue9c%8GptZZNFGxw#sTcWUuSX5c*w}P=D z@wl6iU_gfn^m37i%YW>a4l}i~N=r%U8XgW`TO3`xxTGTrW&VJmV53{&Lf_RkMd=6* zb1_>BNa>MuRCh21gxvPs2l*08d_hNpsO55Gvj3LadOdy13*;V9R)KML<}@ul_GrTB zW?go4U)S!3yM@l!y)JewV-XIgoR-nn(=3D7v5}Jq%iVE#uLj zrLQpJ_u+?k>KG`Qh~1ozMTLc@6cq)@qFv)a?iQS1?ebIsA|39FFEkujA|tmo^6<)N zT)vC3s`xvIQv5EAIj}twm5@KtJK0N>*%7JbAwHu3THLH_^Pv8JG#hW~e_x>ez;!kk z)&n}q{so(1oDyn($(dmX3rTf%KE+LP-D&y9={5osp_I+qX3v4w?QbYo3tDu}exYDD9}KD@wA-?_|1*>Gch^H1$&&{G;O1e17-=Yn)zi|_ ze67~TXb*J5))uSg4&X$8OqfjV`e;+~Xe>y02KGZXrKQr120S8%LWRDM6@juhG&FSJzyWajl4M&)$KfX@4UMzU zi_mSIoSa--CcwUpDaXa}>x;OanVXxNpWpJwkEV`}jzIWeTEV(SFId7(ft2O!Vrzob z`TO$5AQd;t9O`pI=ZRD<92 zg|fYb^$LxwN^Miq)B8F*I|m1~?cNCrDNaQ?Ey0b=(|7OQohSc+#ve(!@(KzHC=|-k ziI$_|(OUxaHwbZzQ>FybdY}So-2MF5x@xE#aydaKDE5|?tg$#{h+!TmCNRp1P>*um z;W!vDdT>GgS#!>lH4xb5yL*9+^%IX7oD6GtuoJWah*VCjh{6#i+m)qOWxgD#UiYKv z@cl9@uVnX{wDQT2FCC1)j9EB3N=Z9<57qfc#~hIgON?}S(vgi~e==0o*_^EeS6>eh zAT5Fg1AS=&UW317b3WgwWW56%ft$yInPJ;|D*kCVG(o2>Mpai=>*(kdR(70-IVC2f*@$`ffdoaEodwNVD+Mq6A?#nORcS9p-mArr@ zR9Caclh9rs9`#e1aL+%zMVyelpP%2t%>BC{nDdQrVC!MS@$p&t*pQGAF#r05tkl$V zxs-BftXVBA`>>qxAO#x= zXEo7X{|bEolN@cWj%bkVd6@LXTzO?>LQM^lGDxT6&(BJUiPZx7tgNg!ZO7Hs{XhXS zEup1kk}V}gF&K0Zh(2}b#?vBbRS2OuKGUCGK(8uqyS;-07W6nMjHZ~kfI#9@!Vf7e zqn)4{CBr=dL>Df+gTxP<`*Kc2#Rsra9*|Ea^X&+T;CD$6r`mDWrludid=ZbxfTT@M z{*Kyk8i_AIF6`MOeJPd6S$f$2Q^~g~sLbeh2>c$f_H8t~e>EN%)`clC^gga^J zwi%I!ajB{P&}Ld&DzYp#$HiYSg$2-PG|L6#>z($ss2Ta#%*;$%JG=2f?}^DtxM-s8 zQ%czSy}&@Wg^5mT-Dw@2Eogh((bsS08~WlGtIxsE5eNiuSFyQZBYsOwZO5b0)wUsZ zr?$Dq%G`MRT+}h>`;4nVS#VaD*0do~uzGlsIdBAYxVY<60|NslcwGljtstOHP5+vb z0=<=t^P8xf)lk1Lo?t>wR996g+Sk4!5S0D(;jW1b@@RBK%afD(Nm2>9FaJ$F-|_aA zrdU6G_;7*D9tIM`U|Bt|Mi70By4_gvJqWnFpI<#Ak+d_emf2P___hrkQtnGoStJr? zA{|44<_OT1uP?IzA8w1mpzFu?3B|=jn=#4H|3q>vG*N+o-F(wWTAvjb7E-A^*aeso zAHfYaGBUESy`^RYvotj|Ra8Fq_JShr?(9rXPVNMM3R)jt3VL2bf`Slvmt%H$;o#yLe$kS9_p_`e?-76*l0?cx7Pd-wN0o3 z(EZ`V2PVGYe8cvC$fZS}>DgIr-?59|$k2?IQUjqYeBt7ajlr literal 0 HcmV?d00001 diff --git a/assets/flresters.png b/assets/flresters.png new file mode 100644 index 0000000000000000000000000000000000000000..94211494661fd053f0258b86525ebd559704e867 GIT binary patch literal 27128 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

$=dPE0&Xh;0u=A);lH#Fuo3W0o4 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 I;qqVq3GD0KxBvhE literal 0 HcmV?d00001 diff --git a/demos/Cargo.toml b/demos/Cargo.toml index be42420..ab0855b 100644 --- a/demos/Cargo.toml +++ b/demos/Cargo.toml @@ -3,7 +3,9 @@ resolver = "2" members = [ "cairo", - "flerrands", + "csv", + "fltodo", + "flightbooker", ] [profile.release] diff --git a/demos/cairo/assets/flcairo.png b/demos/cairo/assets/flcairo.png new file mode 100644 index 0000000000000000000000000000000000000000..9c245719c320d530efc012701d54a7af3dbae4f3 GIT binary patch literal 5271 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 literal 0 HcmV?d00001 diff --git a/demos/cairo/src/main.rs b/demos/cairo/src/main.rs index 1bc0e06..84a73ce 100644 --- a/demos/cairo/src/main.rs +++ b/demos/cairo/src/main.rs @@ -1,164 +1,188 @@ #![forbid(unsafe_code)] +mod model; + use { cairo::{Context, Format, ImageSurface}, flemish::{ app, button::Button, color_themes, draw, + enums::Event, enums::{Align, Color, ColorDepth, Font, Shortcut}, frame::Frame, group::Flex, image::RgbImage, - menu::{MenuButton, MenuFlag}, + menu::{MenuButton, MenuButtonType, MenuFlag}, prelude::*, OnEvent, OnMenuEvent, Sandbox, Settings, }, + model::Model, }; -pub fn main() { - Counter::new().run(Settings { +#[derive(Clone, Copy)] +pub enum Message { + Inc, + Dec, + Quit, +} + +fn main() { + Model::new().run(Settings { size: (640, 360), ignore_esc_close: true, - background: Some(Color::White), + resizable: false, + background: Some(Color::from_u32(0xfdf6e3)), color_map: Some(color_themes::TAN_THEME), - scheme: Some(app::Scheme::Plastic), + scheme: Some(app::Scheme::Base), ..Default::default() }) } -#[derive(Default)] -struct Counter { - value: u8, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - Inc, - Dec, - Quit, -} - -impl Sandbox for Counter { +impl Sandbox for Model { type Message = Message; - fn title(&self) -> String { - String::from("Cairo Buttons") + fn new() -> Self { + Self::default() } - fn new() -> Self { - Self { value: 0u8 } + fn title(&self) -> String { + format!("{} - FlCairo", self.value()) } fn view(&mut self) { - let mut page = Flex::default_fill().column(); - let mut header = Flex::default(); - let menu = MenuButton::default() - .with_label("@#menu") - .on_item_event( - "Command/Increment", - Shortcut::None, - MenuFlag::Normal, - |_| Message::Inc, - ) - .on_item_event( - "Command/Decrement", - Shortcut::None, - MenuFlag::Normal, - |_| Message::Dec, - ) - .on_item_event( - "Quit", - Shortcut::Ctrl | 'q', - MenuFlag::Normal, - |_| Message::Quit, - ); - header.end(); - header.fixed(&menu, 50); - page.set_pad(10); - page.set_margin(10); - page.fixed(&header, 30); - let hero = Flex::default(); + let mut page = Flex::default() + .with_size(600, 200) + .center_of_parent() + .column(); + + let hero = Flex::default(); //HERO crate::cairobutton() .with_label("@#<") - .on_event(|_| Message::Dec); - let mut frame = Frame::default().with_label(&self.value.to_string()); - frame.set_label_size(60); + .on_event(move |_| Message::Dec); + crate::frame(&self.value()).handle(crate::popup); crate::cairobutton() .with_label("@#>") - .on_event(|_| Message::Inc); + .on_event(move |_| Message::Inc); hero.end(); + page.end(); + page.set_pad(0); + page.set_margin(0); } fn update(&mut self, message: Message) { match message { - Message::Inc => { - self.value = self.value.saturating_add(1); - } - Message::Dec => { - self.value = self.value.saturating_sub(1); - } - Message::Quit => { - app::quit(); - } + Message::Inc => self.inc(), + Message::Dec => self.dec(), + Message::Quit => app::quit(), } } } +fn menu() -> MenuButton { + MenuButton::default() + .with_type(MenuButtonType::Popup3) + .with_label("@#menu") + .on_item_event( + "@#+ &Increment", + Shortcut::Ctrl | 'i', + MenuFlag::Normal, + move |_| Message::Inc, + ) + .on_item_event( + "@#- &Decrement", + Shortcut::Ctrl | 'd', + MenuFlag::Normal, + move |_| Message::Dec, + ) + .on_item_event( + "@#1+ Quit", + Shortcut::Ctrl | 'q', + MenuFlag::Normal, + move |_| Message::Quit, + ) +} + +fn popup(_: &mut Frame, event: Event) -> bool { + match event { + Event::Push => match app::event_mouse_button() { + app::MouseButton::Right => { + crate::menu().popup(); + true + } + _ => false, + }, + _ => false, + } +} + +fn frame(value: &str) -> Frame { + let mut element = Frame::default().with_label(value); + element.set_label_size(60); + element +} + fn cairobutton() -> Button { let mut element = Button::default(); element.super_draw(false); - element.draw(|w| { - draw::draw_rect_fill(w.x(), w.y(), w.w(), w.h(), Color::White); - let mut surface = - ImageSurface::create(Format::ARgb32, w.w(), w.h()).expect("Couldn’t create surface"); - crate::draw_surface(&mut surface, w.w(), w.h()); - if !w.value() { + element.draw(|button| { + draw::draw_rect_fill( + button.x(), + button.y(), + button.w(), + button.h(), + Color::from_u32(0xfdf6e3), + ); + let mut surface = ImageSurface::create(Format::ARgb32, button.w(), button.h()) + .expect("Couldn’t create surface"); + crate::draw_surface(&mut surface, button.w(), button.h()); + if !button.value() { cairo_blur::blur_image_surface(&mut surface, 20); } surface - .with_data(|s| { - let mut img = RgbImage::new(s, w.w(), w.h(), ColorDepth::Rgba8).unwrap(); - img.draw(w.x(), w.y(), w.w(), w.h()); + .with_data(|surface| { + RgbImage::new(surface, button.w(), button.h(), ColorDepth::Rgba8) + .unwrap() + .draw(button.x(), button.y(), button.w(), button.h()); }) .unwrap(); draw::set_draw_color(Color::Black); draw::set_font(Font::Helvetica, app::font_size()); - if !w.value() { + if !button.value() { draw::draw_rbox( - w.x() + 1, - w.y() + 1, - w.w() - 6, - w.h() - 6, + button.x() + 1, + button.y() + 1, + button.w() - 6, + button.h() - 6, 15, true, Color::White, ); draw::draw_text2( - &w.label(), - w.x() + 1, - w.y() + 1, - w.w() - 6, - w.h() - 6, + &button.label(), + button.x() + 1, + button.y() + 1, + button.w() - 6, + button.h() - 6, Align::Center, ); } else { draw::draw_rbox( - w.x() + 1, - w.y() + 1, - w.w() - 4, - w.h() - 4, + button.x() + 1, + button.y() + 1, + button.w() - 4, + button.h() - 4, 15, true, Color::White, ); draw::draw_text2( - &w.label(), - w.x() + 1, - w.y() + 1, - w.w() - 4, - w.h() - 4, + &button.label(), + button.x() + 1, + button.y() + 1, + button.w() - 4, + button.h() - 4, Align::Center, ); } diff --git a/demos/cairo/src/model/mod.rs b/demos/cairo/src/model/mod.rs new file mode 100644 index 0000000..0f65d50 --- /dev/null +++ b/demos/cairo/src/model/mod.rs @@ -0,0 +1,18 @@ +pub struct Model { + value: u8, +} + +impl Model { + pub fn default() -> Self { + Self { value: 0u8 } + } + pub fn inc(&mut self) { + self.value = self.value.saturating_add(1); + } + pub fn dec(&mut self) { + self.value = self.value.saturating_sub(1); + } + pub fn value(&self) -> String { + self.value.to_string() + } +} diff --git a/demos/csv/Cargo.toml b/demos/csv/Cargo.toml new file mode 100644 index 0000000..5c385fa --- /dev/null +++ b/demos/csv/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "flcsv" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +flemish = { path = "../../" } +csv = "^1.3" +serde = { version = "^1.0", features = ["derive"] } +lazy_static = "^1.4" +image = { version = "^0.25", default-features = false, features = ["jpeg"] } diff --git a/demos/csv/assets/flcsv.png b/demos/csv/assets/flcsv.png new file mode 100644 index 0000000000000000000000000000000000000000..7b715172f36f6f460d74093115178d2e7d90e7ef GIT binary patch literal 4721 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 literal 0 HcmV?d00001 diff --git a/demos/flerrands/assets/flerrands.gif b/demos/csv/assets/flerrands.gif similarity index 100% rename from demos/flerrands/assets/flerrands.gif rename to demos/csv/assets/flerrands.gif diff --git a/demos/csv/assets/historical_data/GME.csv b/demos/csv/assets/historical_data/GME.csv new file mode 100644 index 0000000..a0925eb --- /dev/null +++ b/demos/csv/assets/historical_data/GME.csv @@ -0,0 +1,152 @@ +Date,Open,High,Low,Close,Adj Close,Volume +2020-03-24,3.950000,4.210000,3.890000,4.160000,4.160000,6805600 +2020-03-25,4.150000,4.490000,4.040000,4.170000,4.170000,3592100 +2020-03-26,4.240000,4.710000,4.240000,4.410000,4.410000,6185700 +2020-03-27,4.940000,5.090000,4.150000,4.220000,4.220000,7024800 +2020-03-30,4.220000,4.270000,3.650000,3.650000,3.650000,3350600 +2020-03-31,3.630000,3.800000,3.500000,3.500000,3.500000,2300900 +2020-04-01,3.450000,3.490000,3.120000,3.250000,3.250000,4568700 +2020-04-02,3.260000,3.350000,2.850000,2.850000,2.850000,4064300 +2020-04-03,2.850000,2.940000,2.570000,2.800000,2.800000,3830400 +2020-04-06,2.860000,3.200000,2.830000,3.090000,3.090000,3340500 +2020-04-07,3.390000,3.440000,3.100000,3.270000,3.270000,2836900 +2020-04-08,3.230000,3.670000,3.200000,3.410000,3.410000,2884500 +2020-04-09,3.600000,4.250000,3.490000,3.890000,3.890000,5908600 +2020-04-13,4.250000,4.760000,4.160000,4.740000,4.740000,6844500 +2020-04-14,5.210000,6.470000,5.140000,5.950000,5.950000,13506600 +2020-04-15,5.660000,5.670000,4.900000,5.270000,5.270000,7499900 +2020-04-16,5.120000,5.440000,4.880000,5.030000,5.030000,3371900 +2020-04-17,5.220000,5.280000,4.430000,4.880000,4.880000,5653200 +2020-04-20,4.850000,5.900000,4.780000,5.610000,5.610000,6085000 +2020-04-21,5.230000,5.300000,4.760000,4.780000,4.780000,4142100 +2020-04-22,5.140000,5.170000,4.830000,4.890000,4.890000,2677800 +2020-04-23,4.750000,4.890000,4.580000,4.700000,4.700000,2265900 +2020-04-24,4.780000,4.850000,4.660000,4.770000,4.770000,2236200 +2020-04-27,4.850000,5.990000,4.810000,5.820000,5.820000,7275100 +2020-04-28,5.920000,6.040000,5.060000,5.640000,5.640000,5200200 +2020-04-29,5.830000,6.090000,5.450000,6.040000,6.040000,3369600 +2020-04-30,5.870000,5.980000,5.640000,5.730000,5.730000,2173300 +2020-05-01,5.650000,6.180000,5.450000,6.050000,6.050000,4005600 +2020-05-04,5.840000,5.930000,5.400000,5.480000,5.480000,4068100 +2020-05-05,5.550000,5.630000,5.350000,5.390000,5.390000,2105900 +2020-05-06,5.400000,5.400000,4.900000,4.930000,4.930000,3369600 +2020-05-07,4.830000,4.930000,4.650000,4.870000,4.870000,2500800 +2020-05-08,4.880000,5.190000,4.820000,4.980000,4.980000,2353700 +2020-05-11,4.920000,4.960000,4.750000,4.760000,4.760000,1699200 +2020-05-12,4.780000,4.920000,4.460000,4.540000,4.540000,2639200 +2020-05-13,4.540000,4.540000,4.070000,4.210000,4.210000,2882900 +2020-05-14,4.150000,4.190000,3.960000,4.130000,4.130000,2004900 +2020-05-15,4.050000,4.450000,4.040000,4.220000,4.220000,1940600 +2020-05-18,4.400000,4.630000,4.360000,4.580000,4.580000,2371700 +2020-05-19,4.580000,4.750000,4.370000,4.440000,4.440000,1840700 +2020-05-20,4.500000,4.690000,4.340000,4.430000,4.430000,2543600 +2020-05-21,4.450000,4.650000,4.400000,4.440000,4.440000,1971900 +2020-05-22,4.460000,4.490000,4.130000,4.180000,4.180000,2379900 +2020-05-26,4.340000,4.610000,4.330000,4.420000,4.420000,3545700 +2020-05-27,4.570000,4.710000,4.360000,4.690000,4.690000,3146600 +2020-05-28,4.750000,4.760000,4.300000,4.330000,4.330000,2183900 +2020-05-29,4.330000,4.420000,4.050000,4.060000,4.060000,3973500 +2020-06-01,4.120000,4.360000,4.020000,4.130000,4.130000,2611600 +2020-06-02,4.270000,4.310000,4.060000,4.180000,4.180000,2369400 +2020-06-03,4.240000,4.520000,4.200000,4.440000,4.440000,3037400 +2020-06-04,4.440000,4.720000,4.370000,4.470000,4.470000,3514300 +2020-06-05,4.290000,4.410000,4.090000,4.140000,4.140000,6274400 +2020-06-08,4.280000,5.140000,4.270000,5.010000,5.010000,10133700 +2020-06-09,5.000000,5.000000,4.550000,4.960000,4.960000,8073400 +2020-06-10,4.780000,5.530000,4.680000,5.070000,5.070000,10606400 +2020-06-11,4.640000,4.900000,4.180000,4.370000,4.370000,6236000 +2020-06-12,4.600000,4.780000,4.390000,4.720000,4.720000,4378200 +2020-06-15,4.500000,4.770000,4.420000,4.690000,4.690000,3909000 +2020-06-16,4.950000,4.950000,4.510000,4.640000,4.640000,3121300 +2020-06-17,4.540000,4.830000,4.530000,4.760000,4.760000,3593500 +2020-06-18,4.670000,4.950000,4.650000,4.950000,4.950000,3423800 +2020-06-19,4.950000,5.080000,4.690000,4.880000,4.880000,7366600 +2020-06-22,4.790000,4.950000,4.720000,4.870000,4.870000,3178900 +2020-06-23,4.950000,4.990000,4.800000,4.830000,4.830000,3205400 +2020-06-24,4.830000,4.840000,4.380000,4.410000,4.410000,2976200 +2020-06-25,4.330000,4.540000,4.300000,4.460000,4.460000,2450800 +2020-06-26,4.480000,4.500000,4.270000,4.350000,4.350000,3801200 +2020-06-29,4.350000,4.520000,4.300000,4.380000,4.380000,2131200 +2020-06-30,4.330000,4.510000,4.200000,4.340000,4.340000,3889000 +2020-07-01,4.310000,4.500000,4.310000,4.440000,4.440000,2303700 +2020-07-02,4.490000,4.510000,4.290000,4.290000,4.290000,1887600 +2020-07-06,4.310000,4.340000,4.190000,4.240000,4.240000,2140900 +2020-07-07,4.200000,4.250000,4.060000,4.090000,4.090000,2456600 +2020-07-08,4.100000,4.290000,4.030000,4.260000,4.260000,2052800 +2020-07-09,4.270000,4.320000,4.130000,4.210000,4.210000,1992600 +2020-07-10,4.200000,4.380000,4.180000,4.340000,4.340000,1410800 +2020-07-13,4.350000,4.550000,4.260000,4.260000,4.260000,4216200 +2020-07-14,4.220000,4.310000,4.070000,4.080000,4.080000,2261600 +2020-07-15,4.130000,4.290000,4.130000,4.190000,4.190000,1474100 +2020-07-16,4.190000,4.200000,4.090000,4.170000,4.170000,1330100 +2020-07-17,4.160000,4.230000,3.940000,3.960000,3.960000,3065900 +2020-07-20,3.950000,4.060000,3.770000,3.850000,3.850000,3401100 +2020-07-21,3.900000,4.090000,3.880000,4.010000,4.010000,3341000 +2020-07-22,4.020000,4.120000,3.920000,4.110000,4.110000,2523500 +2020-07-23,4.090000,4.310000,4.060000,4.110000,4.110000,3237200 +2020-07-24,4.060000,4.230000,4.010000,4.030000,4.030000,2215900 +2020-07-27,4.020000,4.120000,3.950000,4.010000,4.010000,2472700 +2020-07-28,3.960000,4.050000,3.920000,3.940000,3.940000,4555400 +2020-07-29,3.940000,4.180000,3.920000,4.060000,4.060000,2879600 +2020-07-30,4.000000,4.230000,3.970000,4.100000,4.100000,2398500 +2020-07-31,4.060000,4.160000,3.990000,4.010000,4.010000,1879400 +2020-08-03,4.030000,4.250000,4.000000,4.150000,4.150000,2517600 +2020-08-04,4.130000,4.740000,4.130000,4.430000,4.430000,10361400 +2020-08-05,4.500000,4.760000,4.250000,4.630000,4.630000,4925700 +2020-08-06,4.600000,4.660000,4.380000,4.430000,4.430000,1901200 +2020-08-07,4.390000,4.400000,4.060000,4.160000,4.160000,3341100 +2020-08-10,4.200000,4.570000,4.180000,4.330000,4.330000,4561800 +2020-08-11,4.430000,4.570000,4.340000,4.350000,4.350000,3138800 +2020-08-12,4.400000,4.630000,4.360000,4.520000,4.520000,3057600 +2020-08-13,4.520000,4.710000,4.500000,4.640000,4.640000,2128300 +2020-08-14,4.600000,4.830000,4.550000,4.750000,4.750000,3474400 +2020-08-17,4.780000,4.780000,4.560000,4.630000,4.630000,2371000 +2020-08-18,4.610000,4.870000,4.430000,4.810000,4.810000,3834400 +2020-08-19,4.800000,4.840000,4.640000,4.720000,4.720000,2612600 +2020-08-20,4.620000,4.680000,4.510000,4.610000,4.610000,2441200 +2020-08-21,4.600000,5.600000,4.600000,5.030000,5.030000,10642600 +2020-08-24,5.100000,5.130000,4.560000,4.870000,4.870000,4585400 +2020-08-25,4.880000,5.250000,4.880000,4.980000,4.980000,2998700 +2020-08-26,4.970000,5.220000,4.920000,5.110000,5.110000,2779700 +2020-08-27,5.110000,5.380000,5.020000,5.250000,5.250000,3384400 +2020-08-28,5.300000,5.570000,5.220000,5.390000,5.390000,4236900 +2020-08-31,5.770000,7.150000,5.690000,6.680000,6.680000,37976000 +2020-09-01,7.300000,7.820000,6.770000,7.650000,7.650000,23211000 +2020-09-02,7.800000,8.050000,7.110000,7.710000,7.710000,13011100 +2020-09-03,7.880000,8.450000,7.240000,7.820000,7.820000,14344500 +2020-09-04,7.780000,7.920000,7.170000,7.650000,7.650000,7662000 +2020-09-08,7.550000,8.280000,7.480000,7.700000,7.700000,9816600 +2020-09-09,7.960000,7.990000,7.310000,7.350000,7.350000,9068100 +2020-09-10,6.650000,6.950000,6.190000,6.230000,6.230000,15558300 +2020-09-11,6.260000,6.330000,5.870000,6.090000,6.090000,6061200 +2020-09-14,6.800000,7.000000,6.430000,6.910000,6.910000,10119000 +2020-09-15,6.860000,7.260000,6.690000,7.090000,7.090000,5743500 +2020-09-16,7.030000,9.040000,7.030000,8.680000,8.680000,19256300 +2020-09-17,8.570000,9.770000,8.410000,9.200000,9.200000,17026700 +2020-09-18,9.200000,9.770000,8.910000,9.470000,9.470000,17407500 +2020-09-21,9.350000,9.600000,8.380000,8.750000,8.750000,7639800 +2020-09-22,10.450000,11.170000,9.900000,10.560000,10.560000,34752500 +2020-09-23,10.600000,10.860000,9.920000,10.040000,10.040000,10651200 +2020-09-24,9.710000,9.810000,9.010000,9.140000,9.140000,7938800 +2020-09-25,9.190000,10.180000,9.100000,10.020000,10.020000,7515200 +2020-09-28,10.160000,10.260000,9.550000,10.090000,10.090000,6764300 +2020-09-29,10.000000,10.650000,9.930000,10.350000,10.350000,5237600 +2020-09-30,10.250000,10.760000,10.060000,10.200000,10.200000,6079000 +2020-10-01,10.090000,10.250000,9.690000,9.770000,9.770000,4554100 +2020-10-02,9.380000,9.780000,9.300000,9.390000,9.390000,4340500 +2020-10-05,9.440000,9.590000,9.250000,9.460000,9.460000,2805000 +2020-10-06,9.560000,9.840000,9.100000,9.130000,9.130000,4535400 +2020-10-07,9.230000,9.560000,9.170000,9.360000,9.360000,3308600 +2020-10-08,9.540000,13.640000,9.190000,13.490000,13.490000,76453600 +2020-10-09,12.830000,14.800000,11.900000,12.020000,12.020000,77152800 +2020-10-12,11.660000,12.770000,11.400000,11.800000,11.800000,23655700 +2020-10-13,11.730000,12.420000,11.650000,11.880000,11.880000,10179700 +2020-10-14,12.670000,12.680000,12.050000,12.250000,12.250000,10776800 +2020-10-15,11.990000,15.100000,11.990000,13.830000,13.830000,39894800 +2020-10-16,13.770000,13.900000,13.080000,13.310000,13.310000,11651600 +2020-10-19,13.440000,14.500000,13.380000,13.910000,13.910000,13169100 +2020-10-20,14.030000,14.140000,13.670000,13.860000,13.860000,6604000 +2020-10-21,13.900000,14.420000,13.800000,14.100000,14.100000,5361900 +2020-10-22,14.200000,15.870000,14.190000,14.910000,14.910000,16212200 +2020-10-23,15.050000,15.380000,14.550000,15.000000,15.000000,6507300 +2020-10-26,14.930000,15.450000,13.260000,13.450000,13.450000,13376300 diff --git a/demos/csv/assets/historical_data/dlpn.csv b/demos/csv/assets/historical_data/dlpn.csv new file mode 100644 index 0000000..d3802f0 --- /dev/null +++ b/demos/csv/assets/historical_data/dlpn.csv @@ -0,0 +1,55 @@ +Date,Open,High,Low,Close,Adj Close,Volume +2021-01-04,3.480000,3.480000,3.300000,3.390000,3.390000,55400 +2021-01-05,3.360000,3.690000,3.360000,3.610000,3.610000,136500 +2021-01-06,3.590000,3.660000,3.450000,3.450000,3.450000,39700 +2021-01-07,3.490000,3.860000,3.470000,3.580000,3.580000,105200 +2021-01-08,3.590000,3.750000,3.520000,3.690000,3.690000,82000 +2021-01-11,3.710000,3.780000,3.510000,3.630000,3.630000,68100 +2021-01-12,3.720000,5.500000,3.630000,4.610000,4.610000,9075100 +2021-01-13,4.220000,4.400000,4.020000,4.070000,4.070000,748800 +2021-01-14,4.140000,4.280000,4.000000,4.040000,4.040000,681200 +2021-01-15,4.030000,4.050000,3.860000,3.990000,3.990000,596600 +2021-01-19,3.990000,3.990000,3.760000,3.940000,3.940000,363300 +2021-01-20,3.950000,3.980000,3.720000,3.870000,3.870000,273500 +2021-01-21,3.940000,3.980000,3.760000,3.980000,3.980000,232500 +2021-01-22,3.950000,4.250000,3.810000,4.120000,4.120000,521700 +2021-01-25,4.150000,4.290000,3.830000,4.060000,4.060000,309300 +2021-01-26,4.100000,4.100000,3.970000,4.050000,4.050000,283300 +2021-01-27,4.240000,4.980000,4.050000,4.830000,4.830000,2167600 +2021-01-28,4.440000,4.460000,4.050000,4.100000,4.100000,496200 +2021-01-29,4.050000,4.080000,3.800000,3.880000,3.880000,320400 +2021-02-01,3.880000,3.940000,3.820000,3.830000,3.830000,151000 +2021-02-02,3.830000,4.000000,3.830000,3.880000,3.880000,139500 +2021-02-03,3.890000,4.100000,3.890000,4.000000,4.000000,153600 +2021-02-04,4.050000,4.230000,4.010000,4.190000,4.190000,200400 +2021-02-05,4.200000,4.540000,3.960000,4.470000,4.470000,596100 +2021-02-08,4.550000,4.710000,4.500000,4.650000,4.650000,339200 +2021-02-09,4.590000,5.100000,4.590000,5.030000,5.030000,335200 +2021-02-10,5.030000,5.140000,4.620000,4.860000,4.860000,443700 +2021-02-11,4.910000,7.500000,4.610000,5.930000,5.930000,10768000 +2021-02-12,5.500000,5.600000,4.880000,5.200000,5.200000,1079700 +2021-02-16,5.010000,5.180000,4.680000,4.820000,4.820000,697900 +2021-02-17,4.840000,5.260000,4.750000,5.160000,5.160000,583800 +2021-02-18,5.100000,5.690000,5.100000,5.600000,5.600000,548700 +2021-02-19,5.600000,5.960000,5.400000,5.750000,5.750000,369600 +2021-02-22,5.550000,5.890000,5.500000,5.720000,5.720000,139300 +2021-02-23,5.530000,5.580000,4.930000,5.020000,5.020000,221300 +2021-02-24,5.110000,5.400000,5.090000,5.090000,5.090000,64400 +2021-02-25,5.050000,5.150000,4.820000,4.850000,4.850000,86400 +2021-02-26,4.830000,4.980000,4.550000,4.660000,4.660000,74200 +2021-03-01,4.700000,4.980000,4.700000,4.920000,4.920000,80600 +2021-03-02,5.030000,5.030000,4.670000,4.670000,4.670000,46800 +2021-03-03,4.660000,4.770000,4.520000,4.610000,4.610000,67100 +2021-03-04,4.520000,4.580000,4.030000,4.070000,4.070000,134300 +2021-03-05,4.070000,4.140000,3.800000,4.080000,4.080000,84800 +2021-03-08,4.080000,4.640000,3.910000,4.630000,4.630000,80900 +2021-03-09,4.600000,4.660000,4.280000,4.540000,4.540000,66800 +2021-03-10,4.670000,4.760000,4.370000,4.460000,4.460000,38100 +2021-03-11,4.400000,4.900000,4.400000,4.760000,4.760000,127600 +2021-03-12,4.720000,4.800000,4.500000,4.510000,4.510000,24900 +2021-03-15,4.500000,5.190000,4.500000,5.120000,5.120000,118400 +2021-03-16,5.180000,5.710000,4.910000,4.960000,4.960000,185800 +2021-03-17,4.950000,5.400000,4.810000,5.220000,5.220000,53000 +2021-03-18,5.230000,5.500000,5.040000,5.090000,5.090000,183400 +2021-03-19,5.010000,5.690000,5.000000,5.690000,5.690000,264200 +2021-03-22,5.750000,5.750000,5.300000,5.450000,5.450000,134200 \ No newline at end of file diff --git a/demos/csv/assets/historical_data/oil.csv b/demos/csv/assets/historical_data/oil.csv new file mode 100644 index 0000000..66a2e82 --- /dev/null +++ b/demos/csv/assets/historical_data/oil.csv @@ -0,0 +1,82 @@ +Date,Open,High,Low,Close,Adj Close,Volume +2020-11-20,41.700001,42.320000,41.509998,42.150002,42.150002,276340 +2020-11-23,42.459999,43.360001,42.290001,43.060001,43.060001,300717 +2020-11-24,42.840000,45.200001,42.820000,44.910000,44.910000,418527 +2020-11-25,44.820000,46.259998,44.730000,45.709999,45.709999,417182 +2020-11-30,45.340000,45.799999,44.419998,45.340000,45.340000,370755 +2020-12-01,45.080002,45.700001,44.119999,44.549999,44.549999,314919 +2020-12-02,44.380001,45.919998,43.919998,45.279999,45.279999,368854 +2020-12-03,44.990002,45.840000,44.660000,45.639999,45.639999,350768 +2020-12-04,45.639999,46.680000,45.610001,46.259998,46.259998,335453 +2020-12-07,46.150002,46.540001,45.360001,45.759998,45.759998,348871 +2020-12-08,45.660000,45.930000,45.139999,45.599998,45.599998,332641 +2020-12-09,45.599998,46.240002,44.950001,45.520000,45.520000,434976 +2020-12-10,45.689999,47.740002,45.520000,46.779999,46.779999,447529 +2020-12-11,46.970001,47.290001,46.340000,46.570000,46.570000,367305 +2020-12-14,46.730000,47.439999,45.689999,46.990002,46.990002,403543 +2020-12-15,46.990002,47.730000,46.540001,47.619999,47.619999,311169 +2020-12-16,47.599998,47.939999,47.169998,47.820000,47.820000,275331 +2020-12-17,47.849998,48.590000,47.810001,48.360001,48.360001,96536 +2020-12-18,48.430000,49.279999,48.099998,49.099998,49.099998,83711 +2020-12-21,48.540001,48.610001,46.180000,47.740002,47.740002,478098 +2020-12-22,47.930000,47.959999,46.599998,47.020000,47.020000,295737 +2020-12-23,46.790001,48.500000,46.160000,48.119999,48.119999,344306 +2020-12-28,48.230000,48.959999,47.500000,47.619999,47.619999,238462 +2020-12-29,47.720001,48.349998,47.680000,48.000000,48.000000,213778 +2020-12-30,48.130001,48.660000,47.610001,48.400002,48.400002,266957 +2020-12-31,48.349998,48.580002,47.770000,48.520000,48.520000,181894 +2021-01-04,48.400002,49.830002,47.180000,47.619999,47.619999,528525 +2021-01-05,47.380001,50.200001,47.240002,49.930000,49.930000,643191 +2021-01-06,49.820000,50.939999,49.480000,50.630001,50.630001,509365 +2021-01-07,50.529999,51.279999,50.389999,50.830002,50.830002,369292 +2021-01-08,50.930000,52.750000,50.810001,52.240002,52.240002,499416 +2021-01-11,52.580002,52.700001,51.500000,52.250000,52.250000,394822 +2021-01-12,52.180000,53.450001,52.070000,53.209999,53.209999,400200 +2021-01-13,53.310001,53.930000,52.580002,52.910000,52.910000,429898 +2021-01-14,52.840000,53.750000,52.240002,53.570000,53.570000,390087 +2021-01-15,53.799999,53.830002,51.830002,52.360001,52.360001,165668 +2021-01-19,52.000000,53.130001,51.759998,52.980000,52.980000,125964 +2021-01-20,53.130001,53.790001,53.049999,53.240002,53.240002,400301 +2021-01-21,52.930000,53.410000,52.750000,53.130001,53.130001,352397 +2021-01-22,53.099998,53.160000,51.439999,52.270000,52.270000,437484 +2021-01-25,52.169998,52.950001,51.820000,52.770000,52.770000,349076 +2021-01-26,52.910000,53.250000,52.290001,52.610001,52.610001,314538 +2021-01-27,52.759998,53.299999,51.849998,52.849998,52.849998,442376 +2021-01-28,52.650002,53.580002,52.040001,52.340000,52.340000,411406 +2021-01-29,52.150002,53.250000,51.930000,52.200001,52.200001,410914 +2021-02-01,51.990002,53.740002,51.639999,53.549999,53.549999,416067 +2021-02-02,53.480000,55.259998,53.450001,54.759998,54.759998,463718 +2021-02-03,55.049999,56.330002,54.810001,55.689999,55.689999,448693 +2021-02-04,55.959999,56.580002,55.299999,56.230000,56.230000,381404 +2021-02-05,56.459999,57.290001,56.430000,56.849998,56.849998,400833 +2021-02-08,57.060001,58.139999,57.000000,57.970001,57.970001,399724 +2021-02-09,58.110001,58.619999,57.270000,58.360001,58.360001,514698 +2021-02-10,58.450001,58.910000,58.080002,58.680000,58.680000,457535 +2021-02-11,58.400002,58.709999,57.840000,58.240002,58.240002,380445 +2021-02-12,57.939999,59.820000,57.410000,59.470001,59.470001,480822 +2021-02-16,59.980000,60.950001,59.330002,60.049999,60.049999,602332 +2021-02-17,60.240002,61.730000,59.430000,61.139999,61.139999,335419 +2021-02-18,61.680000,62.259998,59.790001,60.520000,60.520000,132035 +2021-02-19,60.200001,60.290001,58.590000,59.240002,59.240002,102427 +2021-02-22,58.880001,61.840000,58.820000,61.490002,61.490002,505773 +2021-02-23,62.160000,63.000000,60.669998,61.669998,61.669998,570677 +2021-02-24,61.290001,63.509998,60.970001,63.220001,63.220001,489933 +2021-02-25,63.389999,63.810001,62.650002,63.529999,63.529999,510357 +2021-02-26,63.459999,63.570000,61.340000,61.500000,61.500000,472723 +2021-03-01,61.950001,62.919998,59.959999,60.639999,60.639999,456679 +2021-03-02,60.230000,61.209999,59.380001,59.750000,59.750000,464133 +2021-03-03,59.549999,61.990002,59.240002,61.279999,61.279999,465311 +2021-03-04,61.080002,64.860001,60.520000,63.830002,63.830002,694628 +2021-03-05,64.160004,66.419998,63.820000,66.089996,66.089996,573168 +2021-03-08,66.680000,67.980003,64.570000,65.050003,65.050003,533376 +2021-03-09,64.730003,65.980003,63.630001,64.010002,64.010002,554564 +2021-03-10,63.840000,64.959999,63.130001,64.440002,64.440002,498488 +2021-03-11,64.699997,66.209999,64.540001,66.019997,66.019997,441063 +2021-03-12,65.959999,66.239998,65.410004,65.610001,65.610001,321714 +2021-03-15,65.559998,66.400002,64.129997,65.389999,65.389999,378009 +2021-03-16,65.330002,65.430000,63.799999,64.800003,64.800003,317537 +2021-03-17,64.750000,65.339996,63.599998,64.599998,64.599998,254724 +2021-03-18,64.419998,64.820000,58.200001,60.000000,60.000000,143597 +2021-03-19,59.560001,61.720001,58.939999,61.419998,61.419998,84171 +2021-03-22,61.549999,61.900002,60.389999,61.549999,61.549999,84171 +2021-03-23,57.400002,57.650002,57.290001,57.450001,57.450001,3606 \ No newline at end of file diff --git a/demos/csv/src/main.rs b/demos/csv/src/main.rs new file mode 100644 index 0000000..b8d331c --- /dev/null +++ b/demos/csv/src/main.rs @@ -0,0 +1,138 @@ +#![forbid(unsafe_code)] + +mod model; + +use { + flemish::{ + app, + browser::{Browser, BrowserType}, + button::Button, + color_themes, draw, + enums::{Color, FrameType}, + frame::Frame, + group::Flex, + prelude::*, + OnEvent, Sandbox, Settings, + }, + model::Model, +}; + +const NAME: &str = "FlCSV"; +const PAD: i32 = 10; +const HEIGHT: i32 = PAD * 3; +const WIDTH: i32 = HEIGHT * 3; + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +#[derive(Clone)] +pub enum Message { + Choice(usize), + Save, +} + +impl Sandbox for Model { + type Message = Message; + + fn title(&self) -> String { + String::from(NAME) + } + + fn new() -> Self { + Self::default() + } + + fn view(&mut self) { + let mut page = Flex::default_fill(); + { + let mut left = Flex::default_fill().column(); + crate::browser("Browser", self.clone()).on_event(move |browser| { + Message::Choice((browser.value() as usize).saturating_sub(1)) + }); + crate::button("Load image", &mut left).on_event(move |_| Message::Save); + left.end(); + left.set_pad(PAD); + page.fixed(&left, WIDTH); + } + crate::frame("Canvas", self.clone()); + page.end(); + page.set_pad(PAD); + page.set_margin(PAD); + } + + fn update(&mut self, message: Message) { + match message { + Message::Save => self.init(), + Message::Choice(value) => self.choice(value), + } + } +} + +fn browser(tooltip: &str, value: Model) -> Browser { + let mut element = Browser::default().with_type(BrowserType::Hold); + element.set_tooltip(tooltip); + if !value.temp.is_empty() { + for item in value.temp { + element.add(&item); + } + element.select(value.curr as i32 + 1); + } + element +} + +fn button(tooltip: &str, flex: &mut Flex) -> Button { + let mut element = Button::default().with_label("@#refresh"); + element.set_tooltip(tooltip); + flex.fixed(&element, HEIGHT); + element +} + +fn frame(tooltip: &str, value: Model) { + let mut element = Frame::default(); + element.set_frame(FrameType::DownBox); + element.set_tooltip(tooltip); + element.set_color(Color::Black); + element.draw(move |frame| { + if !value.temp.is_empty() { + if let Some(data) = value.cash.get(&value.temp[value.curr]) { + let mut highest = data + .iter() + .map(|elem| elem.low) + .collect::>() + .iter() + .cloned() + .fold(f64::NAN, f64::max); + highest += (highest.to_string().len() * 10) as f64 / 3.; + let factor = frame.h() as f64 / highest; + if !data.is_empty() { + let step = frame.w() / data.len() as i32; + let mut idx = frame.x() + step; + for elem in data { + let open = frame.h() - (elem.open * factor) as i32; + let high = frame.h() - (elem.high * factor) as i32; + let low = frame.h() - (elem.low * factor) as i32; + let close = frame.h() - (elem.close * factor) as i32; + draw::draw_line(idx, high, idx, low); + let col = if close > open { + Color::Red + } else { + Color::Green + }; + draw::set_draw_color(col); + draw::draw_rectf(idx - 2, open, 4, i32::abs(close - open)); + draw::set_draw_color(Color::White); + idx += step; + } + }; + } + } + }); +} diff --git a/demos/csv/src/model/mod.rs b/demos/csv/src/model/mod.rs new file mode 100644 index 0000000..36b7982 --- /dev/null +++ b/demos/csv/src/model/mod.rs @@ -0,0 +1,63 @@ +use { + csv::Reader, + serde::Deserialize, + std::{collections::HashMap, fs}, +}; + +#[derive(Debug, Deserialize, Clone)] +pub struct Price { + #[serde(rename = "Date")] + _date: String, + #[serde(rename = "Open")] + pub open: f64, + #[serde(rename = "High")] + pub high: f64, + #[serde(rename = "Low")] + pub low: f64, + #[serde(rename = "Close")] + pub close: f64, + #[serde(rename = "Volume")] + _volume: usize, +} + +#[derive(Debug, Clone)] +pub struct Model { + pub cash: HashMap>, + pub temp: Vec, + pub curr: usize, + pub save: bool, +} + +impl Model { + pub fn default() -> Self { + Self { + cash: HashMap::new(), + temp: Vec::new(), + save: false, + curr: 0, + } + } + pub fn init(&mut self) { + for file in std::fs::read_dir("assets/historical_data").unwrap() { + let entry = file.unwrap().file_name().into_string().unwrap(); + if entry.ends_with(".csv") { + self.temp + .push(entry.strip_suffix(".csv").unwrap().to_string()); + } + self.choice(0); + } + } + pub fn choice(&mut self, curr: usize) { + if self.cash.contains_key(&self.temp[curr]) { + self.curr = curr; + } else if let Ok(data) = fs::read(format!("assets/historical_data/{}.csv", self.temp[curr])) + { + let mut prices: Vec = Vec::new(); + for result in Reader::from_reader(data.as_slice()).deserialize() { + prices.push(result.unwrap()); + } + self.cash.insert(self.temp[curr].clone(), prices); + self.curr = curr; + } + } +} diff --git a/demos/flightbooker/Cargo.toml b/demos/flightbooker/Cargo.toml new file mode 100644 index 0000000..bf974ab --- /dev/null +++ b/demos/flightbooker/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "flightbooker" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +flemish = { path = "../../" } +chrono = "^0.4" diff --git a/demos/flightbooker/assets/flightbooker.png b/demos/flightbooker/assets/flightbooker.png new file mode 100644 index 0000000000000000000000000000000000000000..c4a6a97bd15d0d7f942928529d0f327b6ac2d2a5 GIT binary patch literal 8593 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 literal 0 HcmV?d00001 diff --git a/demos/flightbooker/src/main.rs b/demos/flightbooker/src/main.rs new file mode 100644 index 0000000..b1fec32 --- /dev/null +++ b/demos/flightbooker/src/main.rs @@ -0,0 +1,126 @@ +#![forbid(unsafe_code)] + +mod model; + +use { + flemish::{ + app, button::Button, color_themes, dialog::alert_default, enums::FrameType, frame::Frame, + group::Flex, input::Input, menu::Choice, prelude::*, OnEvent, Sandbox, Settings, + }, + model::Model, +}; + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +const NAME: &str = "7GUI: Flightbooker"; +const PAD: i32 = 10; +const HEIGHT: i32 = PAD * 3; +const WIDTH: i32 = PAD * 12; + +#[derive(Clone)] +pub enum Message { + Direct(i32), + Start(String), + Back(String), + Book, +} + +impl Sandbox for Model { + type Message = Message; + + fn title(&self) -> String { + String::from(NAME) + } + + fn new() -> Self { + Self::default() + } + + fn view(&mut self) { + let mut page = Flex::default() + .with_size(PAD * 26, PAD * 17) + .center_of_parent(); + { + page.fixed(&Frame::default(), WIDTH); + let mut right = Flex::default().column(); + crate::choice(self.direct, &mut right) + .with_label("Direct") + .on_event(move |choice| Message::Direct(choice.value())); + crate::input(&self.start, self.start_active) + .with_label("Start") + .on_event(move |input| Message::Start(input.value())); + crate::input(&self.back, self.back_active) + .with_label("Back") + .on_event(move |input| Message::Back(input.value())); + crate::button(self.book_active, &mut right) + .with_label("Book") + .clone() + .on_event(move |_| Message::Book); + right.end(); + right.set_pad(PAD); + page.end(); + page.set_pad(0); + page.set_margin(PAD); + page.set_frame(FrameType::UpBox); + } + page.set_margin(PAD); + page.set_pad(PAD); + } + + fn update(&mut self, message: Message) { + match message { + Message::Direct(value) => self.direct(value), + Message::Start(value) => self.start(value), + Message::Back(value) => self.back(value), + Message::Book => { + alert_default(&if self.direct == 0 { + format!("You have booked a one-way flight for {}.", self.start) + } else { + format!( + "You have booked a return flight from {} to {}", + self.start, self.back + ) + }); + } + } + } +} + +fn input(value: &str, active: bool) -> Input { + let mut element = Input::default(); + element.set_value(value); + if active { + element.activate(); + } else { + element.deactivate(); + }; + element +} + +fn choice(value: i32, flex: &mut Flex) -> Choice { + let mut element = Choice::default(); + element.add_choice("one-way flight|return flight"); + element.set_value(value); + flex.fixed(&element, HEIGHT); + element +} + +fn button(active: bool, flex: &mut Flex) -> Button { + let mut element = Button::default(); + if active { + element.activate(); + } else { + element.deactivate(); + }; + flex.fixed(&element, HEIGHT); + element +} diff --git a/demos/flightbooker/src/model/mod.rs b/demos/flightbooker/src/model/mod.rs new file mode 100644 index 0000000..45efd5f --- /dev/null +++ b/demos/flightbooker/src/model/mod.rs @@ -0,0 +1,57 @@ +use chrono::{offset::Local, NaiveDate}; + +pub struct Model { + pub direct: i32, + pub start: String, + pub back: String, + pub start_active: bool, + pub back_active: bool, + pub book_active: bool, +} + +impl Model { + pub fn direct(&mut self, value: i32) { + self.direct = value; + self.refresh(); + } + pub fn start(&mut self, value: String) { + self.start = value; + self.refresh(); + } + pub fn back(&mut self, value: String) { + self.back = value; + self.refresh(); + } + pub fn default() -> Self { + let current = Local::now() + .naive_local() + .date() + .format("%Y-%m-%d") + .to_string(); + Self { + direct: 0, + start: current.clone(), + start_active: true, + back: current, + back_active: false, + book_active: false, + } + } + pub fn refresh(&mut self) { + if self.direct == 0 { + self.back_active = false; + self.book_active = get_date(&self.start).is_ok(); + } else { + self.back_active = true; + let start_date = get_date(&self.start); + let back_date = get_date(&self.back); + self.book_active = start_date.is_ok() + && back_date.is_ok() + && start_date.unwrap() <= back_date.unwrap(); + } + } +} + +fn get_date(value: &str) -> Result { + NaiveDate::parse_from_str(value, "%Y-%m-%d") +} diff --git a/demos/flerrands/Cargo.toml b/demos/fltodo/Cargo.toml similarity index 100% rename from demos/flerrands/Cargo.toml rename to demos/fltodo/Cargo.toml diff --git a/demos/fltodo/assets/fltodo.gif b/demos/fltodo/assets/fltodo.gif new file mode 100644 index 0000000000000000000000000000000000000000..cc09ebc44861944d8f96f317340fa703cf097c19 GIT binary patch literal 15610 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::new(env::var("HOME").unwrap() + "/.config/" + NAME); + Model::new().run(Settings { + size: (360, 640), + resizable: false, ignore_esc_close: true, color_map: Some(color_themes::DARK_THEME), scheme: Some(app::Scheme::Base), @@ -29,27 +31,12 @@ pub fn main() { }) } -const NAME: &str = "FlErrands"; -const PATH: &str = "/.config/"; +const NAME: &str = "FlTodo"; const PAD: i32 = 10; const HEIGHT: i32 = PAD * 3; -const WINDOW_WIDTH: i32 = 360; -const WINDOW_HEIGHT: i32 = 640; - -#[derive(Deserialize, Serialize)] -struct Task { - status: bool, - description: String, -} - -#[derive(Deserialize, Serialize)] -struct Model { - size: (i32, i32), - tasks: Vec, -} #[derive(Clone)] -enum Message { +pub enum Message { New(String), Delete(usize), Check(usize), @@ -65,11 +52,8 @@ impl Sandbox for Model { } fn new() -> Self { - let file = env::var("HOME").unwrap() + PATH + NAME; - let default = Self { - size: (WINDOW_WIDTH, WINDOW_HEIGHT), - tasks: Vec::new(), - }; + let file = app::GlobalState::::get().with(move |model| model.clone()); + let default = Self { tasks: Vec::new() }; if let Ok(value) = fs::read(file) { if let Ok(value) = rmp_serde::from_slice(&value) { value @@ -86,11 +70,17 @@ impl Sandbox for Model { let mut header = Flex::default(); // HEADER header.fixed(&crate::menu(), 50); let description = Input::default(); - let add = Button::default().with_label("@#+"); - header.fixed(&add, HEIGHT); - add.on_event(move |_| Message::New(description.value())); + header.fixed( + &Button::default() + .with_label("@#+") + .clone() + .on_event(move |_| Message::New(description.value())), + HEIGHT, + ); header.end(); - let scroll = Scroll::default().with_size(324, 600); + let scroll = Scroll::default() + .with_size(324, 600) + .with_type(ScrollType::Vertical); let mut hero = Flex::default_fill().column(); // HERO for (idx, task) in self.tasks.iter().enumerate() { let mut row = Flex::default(); @@ -133,30 +123,21 @@ impl Sandbox for Model { fn update(&mut self, message: Message) { match message { - Message::Quit => self.quit(), + Message::Quit => { + let file = app::GlobalState::::get().with(move |model| model.clone()); + self.save(file); + app::quit(); + } Message::Delete(idx) => { self.tasks.remove(idx); } Message::Change((idx, value)) => self.tasks[idx].description = value, - Message::Check(idx) => self.tasks[idx].status = !self.tasks[idx].status, - Message::New(description) => self.tasks.push(Task { - status: false, - description, - }), + Message::Check(idx) => self.check(idx), + Message::New(description) => self.add(description), } } } -impl Model { - fn quit(&mut self) { - let file = env::var("HOME").unwrap() + PATH + NAME; - let window = app::first_window().unwrap(); - self.size = (window.width(), window.height()); - fs::write(file, rmp_serde::to_vec(&self).unwrap()).unwrap(); - app::quit(); - } -} - fn menu() -> MenuButton { let mut element = MenuButton::default().with_label("@#menu").on_item_event( "@#1+ &Quit", diff --git a/demos/fltodo/src/model/mod.rs b/demos/fltodo/src/model/mod.rs new file mode 100644 index 0000000..da32807 --- /dev/null +++ b/demos/fltodo/src/model/mod.rs @@ -0,0 +1,30 @@ +use { + serde::{Deserialize, Serialize}, + std::fs, +}; + +#[derive(Deserialize, Serialize)] +pub struct Task { + pub status: bool, + pub description: String, +} + +#[derive(Deserialize, Serialize)] +pub struct Model { + pub tasks: Vec, +} + +impl Model { + pub fn check(&mut self, idx: usize) { + self.tasks[idx].status = !self.tasks[idx].status + } + pub fn add(&mut self, description: String) { + self.tasks.push(Task { + status: false, + description, + }) + } + pub fn save(&mut self, file: String) { + fs::write(file, rmp_serde::to_vec(&self).unwrap()).unwrap(); + } +} diff --git a/examples/checkbutton.rs b/examples/checkbutton.rs new file mode 100644 index 0000000..a04ee71 --- /dev/null +++ b/examples/checkbutton.rs @@ -0,0 +1,84 @@ +#![forbid(unsafe_code)] + +use flemish::{ + app, button::CheckButton, color_themes, enums::FrameType, group::Flex, prelude::*, OnEvent, + Sandbox, Settings, +}; + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +const PAD: i32 = 10; + +struct Model { + default: bool, + styled: bool, + custom: bool, +} + +#[derive(Clone, Copy)] +enum Message { + DefaultToggled(bool), + CustomToggled(bool), + StyledToggled(bool), +} + +impl Sandbox for Model { + type Message = Message; + + fn new() -> Self { + Self { + default: true, + styled: false, + custom: false, + } + } + + fn title(&self) -> String { + String::from("CheckButton - Flemish") + } + + fn view(&mut self) { + let mut page = Flex::default() + .with_size(300, 150) + .center_of_parent() + .column(); + { + crate::check(self.default) + .on_event(move |check| Message::DefaultToggled(check.value())); + crate::check(self.styled).on_event(move |check| Message::StyledToggled(check.value())); + crate::check(self.custom).on_event(move |check| Message::CustomToggled(check.value())); + } + page.end(); + page.set_pad(PAD); + page.set_frame(FrameType::UpBox); + } + + fn update(&mut self, message: Message) { + match message { + Message::DefaultToggled(value) => { + self.default = value; + } + Message::StyledToggled(value) => { + self.styled = value; + } + Message::CustomToggled(value) => { + self.custom = value; + } + } + } +} + +fn check(value: bool) -> CheckButton { + let mut element = CheckButton::default(); + element.set_value(value); + element +} diff --git a/examples/counter.rs b/examples/counter.rs index 424a5d7..9f4c55e 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -1,13 +1,13 @@ #![forbid(unsafe_code)] use flemish::{ - app, button::Button, color_themes, frame::Frame, group::Flex, prelude::*, valuator::Dial, + app, button::Button, color_themes, enums::FrameType, frame::Frame, group::Flex, prelude::*, OnEvent, Sandbox, Settings, }; pub fn main() { - Counter::new().run(Settings { - size: (100, 300), + Model::new().run(Settings { + size: (640, 360), resizable: false, ignore_esc_close: true, color_map: Some(color_themes::DARK_THEME), @@ -16,63 +16,64 @@ pub fn main() { }) } -const PAD: i32 = 10; -const DIAL: u8 = 120; +#[derive(Debug, Clone, Copy)] +enum Message { + Inc, + Dec, +} -struct Counter { +struct Model { value: u8, } -#[derive(Debug, Clone, Copy)] -enum Message { - IncrementPressed, - DecrementPressed, +impl Model { + fn default() -> Self { + Self { value: 0u8 } + } + fn inc(&mut self) { + self.value = self.value.saturating_add(1); + } + fn dec(&mut self) { + self.value = self.value.saturating_sub(1); + } + fn value(&self) -> String { + self.value.to_string() + } } -impl Sandbox for Counter { +impl Sandbox for Model { type Message = Message; fn new() -> Self { - Self { value: 0 } + Self::default() } fn title(&self) -> String { - String::from("Counter - fltk-rs") + format!("{} - 7GUI: Counter", self.value()) } - fn update(&mut self, message: Message) { - match message { - Message::IncrementPressed => { - if self.value == DIAL - 1 { - self.value = 0; - } else { - self.value += 1; - } - } - Message::DecrementPressed => { - if self.value == 0 { - self.value = DIAL - 1; - } else { - self.value -= 1; - } - } + fn view(&mut self) { + let mut page = Flex::default().with_size(300, 100).center_of_parent(); + { + Button::default() + .with_label("@#<") + .on_event(|_| Message::Dec); + Frame::default() + .with_label(&self.value.to_string()) + .set_frame(FrameType::UpBox); + Button::default() + .with_label("@#>") + .on_event(|_| Message::Inc); } + page.end(); + page.set_pad(0); + page.set_margin(10); } - fn view(&mut self) { - let mut page = Flex::default_fill().column(); - Button::default() - .with_label("Increment") - .on_event(|_| Message::IncrementPressed); - Frame::default().with_label(&self.value.to_string()); - Button::default() - .with_label("Decrement") - .on_event(|_| Message::DecrementPressed); - let mut dial = Dial::default(); - dial.set_maximum((DIAL / 4 * 3) as f64); - dial.set_value(self.value as f64); - dial.deactivate(); - page.end(); - page.set_margin(PAD); + fn update(&mut self, message: Message) { + match message { + Message::Inc => self.inc(), + Message::Dec => self.dec(), + } } } diff --git a/examples/crud.rs b/examples/crud.rs new file mode 100644 index 0000000..7187a5e --- /dev/null +++ b/examples/crud.rs @@ -0,0 +1,236 @@ +#![forbid(unsafe_code)] + +use flemish::{ + app, + browser::{Browser, BrowserType}, + button::Button, + color_themes, + frame::Frame, + group::Flex, + input::Input, + prelude::*, + OnEvent, Sandbox, Settings, +}; + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +const PAD: i32 = 10; +const HEIGHT: i32 = PAD * 3; +const WIDTH: i32 = HEIGHT * 3; + +#[derive(Debug, Clone)] +enum Message { + Inc, + Dec, + Upd, + Choice(usize), + Filter(String), + Name(String), + Surname(String), + Secretname(String), +} + +#[derive(Debug, Clone)] +struct Person { + name: String, + surname: String, + secretname: String, +} + +impl Person { + fn default() -> Self { + Self { + name: String::new(), + surname: String::new(), + secretname: String::new(), + } + } +} + +#[derive(Debug, Clone)] +struct Model { + filter: String, + inc: bool, + dec: bool, + upd: bool, + list: Vec, + temp: Person, + curr: usize, +} + +impl Model { + fn default() -> Self { + Self { + filter: String::new(), + inc: true, + dec: false, + upd: false, + list: Vec::from([Person::default()]), + temp: Person::default(), + curr: 0, + } + } + fn inc(&mut self) { + self.list.push(self.temp.clone()); + self.curr = self.curr.saturating_add(1); + self.temp = Person::default(); + } + fn dec(&mut self) { + self.list.remove(self.curr); + self.curr = self.curr.saturating_sub(1); + self.temp = self.list[self.curr].clone(); + } + fn upd(&mut self) { + self.list[self.curr] = self.temp.clone(); + } + fn name(&mut self, value: String) { + self.temp.name = value; + } + fn sur(&mut self, value: String) { + self.temp.surname = value; + } + fn sec(&mut self, value: String) { + self.temp.secretname = value; + } +} + +impl Sandbox for Model { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("7GUI: CRUD") + } + + fn view(&mut self) { + let mut page = Flex::default_fill(); + { + let mut left = Flex::default_fill().column(); + + let mut row = Flex::default(); + row.fixed(&Frame::default(), HEIGHT); + crate::input(&self.filter) + .with_label("@#search") + .on_event(move |input| Message::Filter(input.value())); + row.end(); + left.fixed(&row, HEIGHT); + + crate::browser("Browser", self).on_event(move |browser| { + Message::Choice((browser.value() as usize).saturating_sub(1)) + }); + + let mut row = Flex::default(); + crate::button(self.inc) + .with_label("@#+ Add") + .on_event(move |_| Message::Inc); + crate::button(self.upd) + .with_label("@#refresh Update") + .on_event(move |_| Message::Upd); + crate::button(self.dec) + .with_label("@#1+ Delete") + .on_event(move |_| Message::Dec); + row.end(); + row.set_pad(PAD); + left.fixed(&row, HEIGHT); + + left.end(); + left.set_pad(PAD); + page.fixed(&Frame::default(), WIDTH); + + let mut right = Flex::default_fill().column(); + right.fixed( + &crate::input(&self.temp.name) + .with_label("Name:") + .clone() + .on_event(move |input| Message::Name(input.value())), + HEIGHT, + ); + right.fixed( + &crate::input(&self.temp.surname) + .with_label("Surname:") + .clone() + .on_event(move |input| Message::Surname(input.value())), + HEIGHT, + ); + right.fixed( + &crate::input(&self.temp.secretname) + .with_label("Secretname:") + .clone() + .on_event(move |input| Message::Secretname(input.value())), + HEIGHT, + ); + right.end(); + } + page.end(); + page.set_pad(0); + page.set_margin(10); + } + + fn update(&mut self, message: Message) { + match message { + Message::Choice(value) => { + self.curr = value; + self.upd = true; + if self.list.len() > 1 { + self.dec = true; + } + } + Message::Filter(value) => self.filter = value, + Message::Name(value) => self.name(value), + Message::Surname(value) => self.sur(value), + Message::Secretname(value) => self.sec(value), + Message::Inc => self.inc(), + Message::Upd => self.upd(), + Message::Dec => self.dec(), + } + } +} + +fn button(value: bool) -> Button { + let mut element = Button::default(); + if value { + element.activate(); + } else { + element.deactivate(); + } + element +} + +fn browser(tooltip: &str, value: &Model) -> Browser { + let mut element = Browser::default().with_type(BrowserType::Hold); + element.set_tooltip(tooltip); + if !value.list.is_empty() { + for item in &value.list { + if item + .name + .to_lowercase() + .starts_with(&value.filter.to_lowercase()) + { + element.add(&format!( + "{} {} {}", + item.name, item.surname, item.secretname + )); + } + } + element.select(value.curr as i32 + 1); + } + element +} + +fn input(value: &str) -> Input { + let mut element = Input::default(); + element.set_value(value); + element +} diff --git a/examples/flcalculator.rs b/examples/flcalculator.rs index 166af7c..43eaf2c 100644 --- a/examples/flcalculator.rs +++ b/examples/flcalculator.rs @@ -17,6 +17,7 @@ use { }; pub fn main() { + app::GlobalState::::new(env::var("HOME").unwrap() + PATH + NAME); Model::new().run(Settings { size: (360, 640), resizable: false, @@ -27,6 +28,13 @@ pub fn main() { }) } +#[derive(PartialEq, Clone)] +enum Message { + Click(String), + Theme, + Quit, +} + #[derive(Clone)] struct Model { prev: String, @@ -36,18 +44,64 @@ struct Model { theme: bool, } -#[derive(PartialEq, Clone)] -enum Message { - Click(String), - Theme, - Quit, +impl Model { + fn theme(&mut self) { + self.theme = !self.theme; + } + fn click(&mut self, value: String) { + match value.as_str() { + "/" | "x" | "+" | "-" | "%" => { + if self.operation.is_empty() { + self.operation.push_str(&value); + self.prev = self.current.clone(); + } else { + self.equil(); + self.operation = String::from("="); + } + self.output + .push_str(&format!("{} {}", self.prev, self.operation)); + self.current = String::from("0"); + } + "=" => self.equil(), + "CE" => { + self.output.clear(); + self.operation.clear(); + self.current = String::from("0"); + self.prev = String::from("0"); + } + "@<-" => { + let label = self.current.clone(); + self.current = if label.len() > 1 { + String::from(&label[..label.len() - 1]) + } else { + String::from("0") + }; + } + "C" => self.current = String::from("0"), + "." => { + if !self.current.contains('.') { + self.current.push('.'); + } + } + _ => { + if self.current == "0" { + self.current.clear(); + } + self.current = self.current.clone() + &value; + } + }; + } } impl Sandbox for Model { type Message = Message; + fn title(&self) -> String { + String::from(NAME) + } + fn new() -> Self { - let file = env::var("HOME").unwrap() + PATH + NAME; + let file = app::GlobalState::::get().with(move |model| model.clone()); let theme: bool = match Path::new(&file).exists() { true => fs::read(&file).unwrap()[0], false => 0, @@ -61,10 +115,6 @@ impl Sandbox for Model { } } - fn title(&self) -> String { - String::from(NAME) - } - fn view(&mut self) { let menu = crate::menu(self.theme as usize); let mut page = Flex::default_fill().column(); @@ -122,57 +172,15 @@ impl Sandbox for Model { fn update(&mut self, message: Message) { match message { Message::Quit => self.quit(), - Message::Theme => { - self.theme = !self.theme; - } - Message::Click(value) => match value.as_str() { - "/" | "x" | "+" | "-" | "%" => { - if self.operation.is_empty() { - self.operation.push_str(&value); - self.prev = self.current.clone(); - } else { - self.equil(); - self.operation = String::from("="); - } - self.output - .push_str(&format!("{} {}", self.prev, self.operation)); - self.current = String::from("0"); - } - "=" => self.equil(), - "CE" => { - self.output.clear(); - self.operation.clear(); - self.current = String::from("0"); - self.prev = String::from("0"); - } - "@<-" => { - let label = self.current.clone(); - self.current = if label.len() > 1 { - String::from(&label[..label.len() - 1]) - } else { - String::from("0") - }; - } - "C" => self.current = String::from("0"), - "." => { - if !self.current.contains('.') { - self.current.push('.'); - } - } - _ => { - if self.current == "0" { - self.current.clear(); - } - self.current = self.current.clone() + &value; - } - }, + Message::Theme => self.theme(), + Message::Click(value) => self.click(value), }; } } impl Model { fn quit(&self) { - let file = env::var("HOME").unwrap() + PATH + NAME; + let file = app::GlobalState::::get().with(move |model| model.clone()); fs::write(file, [self.theme as u8]).unwrap(); app::quit(); } diff --git a/examples/fldialect.rs b/examples/fldialect.rs index f9c672e..403d28e 100644 --- a/examples/fldialect.rs +++ b/examples/fldialect.rs @@ -6,9 +6,9 @@ use { button::{Button, ButtonType}, color_themes, dialog::{alert_default, FileChooser, FileChooserType, HelpDialog}, - enums::{Color, Cursor, Event, Font, FrameType, Shortcut}, + enums::{Color, Font, FrameType, Shortcut}, frame::Frame, - group::{Flex, FlexType}, + group::Flex, menu::{Choice, MenuButton, MenuFlag}, prelude::*, text::{TextBuffer, TextEditor, WrapMode}, @@ -20,12 +20,13 @@ use { fn main() { if crate::once() { + app::GlobalState::::new(env::var("HOME").unwrap() + PATH + NAME); let mut app = Model::new(); app.run(Settings { size: (app.width, app.height), pos: (app.vertical, app.horizontal), ignore_esc_close: true, - resizable: true, + resizable: false, color_map: Some(color_themes::DARK_THEME), scheme: Some(app::Scheme::Base), ..Default::default() @@ -43,7 +44,6 @@ struct Model { speak: bool, font: u8, size: u8, - spinner: u8, source: String, target: String, lang: Vec, @@ -73,7 +73,7 @@ impl Sandbox for Model { } fn new() -> Self { - let file = env::var("HOME").unwrap() + PATH + NAME; + let file = app::GlobalState::::get().with(move |model| model.clone()); let params: Vec = if Path::new(&file).exists() { if let Ok(value) = fs::read(&file) { if value.len() == DEFAULT.len() { @@ -100,9 +100,8 @@ impl Sandbox for Model { to: params[5], font: params[6], size: params[7], - vertical: ((w + width as f64) / 4_f64) as i32, - horizontal: ((h + height as f64) / 4_f64) as i32, - spinner: 0, + vertical: ((w - width as f64) / 2_f64) as i32, + horizontal: ((h - height as f64) / 2_f64) as i32, speak: false, source: String::new(), target: String::new(), @@ -112,105 +111,55 @@ impl Sandbox for Model { fn view(&mut self) { let mut page = Flex::default_fill().column(); - - let mut header = Flex::default(); - header.fixed(&crate::menu(), HEIGHT); - Frame::default(); - crate::choice("From", &self.lang.join("|"), self.from, &mut header) - .on_event(move |choice| Message::From(choice.value() as u8)); - crate::button("Switch", "@#refresh", &mut header) - .on_event(move |_| Message::Switch); - crate::choice("To", &self.lang.join("|"), self.to, &mut header) - .on_event(move |choice| Message::To(choice.value() as u8)); - Frame::default(); - let mut button = crate::button("Speak", "@#<", &mut header).with_type(ButtonType::Toggle); - button.set(self.speak); - button.on_event(move |button| Message::Speak(button.value())); - header.end(); - - let mut hero = Flex::default().column().with_id("HERO"); - crate::text("Source", &self.source, self.font, self.size) - .on_event(move |text| Message::Source(text.buffer().unwrap().text())); - Frame::default().with_id("Handle").handle(move |frame, event| { - let mut flex = app::widget_from_id::("HERO").unwrap(); - match event { - Event::Push => true, - Event::Drag => { - let child = flex.child(0).unwrap(); - match flex.get_type() { - FlexType::Column => { - if (flex.y()..=flex.height() + flex.y() - frame.height()) - .contains(&app::event_y()) - { - flex.fixed(&child, app::event_y() - flex.y()); - } - } - FlexType::Row => { - if (flex.x()..=flex.width() + flex.x() - frame.width()) - .contains(&app::event_x()) - { - flex.fixed(&child, app::event_x() - flex.x()); - } - } - } - app::redraw(); - true - } - Event::Enter => { - frame.window().unwrap().set_cursor( - match flex.get_type() { - FlexType::Column => Cursor::NS, - FlexType::Row => Cursor::WE, - } - ); - true - } - Event::Leave => { - frame.window().unwrap().set_cursor(Cursor::Arrow); - true - } - _ => false, - } - }); - crate::text("Target", &self.target, self.font, self.size); - hero.end(); - - let mut footer = Flex::default(); //FOOTER - crate::button("Open...", "@#fileopen", &mut footer).on_event(move |_| Message::Open); - Frame::default(); - crate::choice("Font", &app::fonts().join("|"), self.font, &mut footer) - .on_event(move |choice| Message::Font(choice.value() as u8)); - crate::button("Translate", "@#circle", &mut footer).on_event(move |_| Message::Translate); - crate::counter("Size", self.size as f64, &mut footer).with_type(CounterType::Simple) - .on_event(move |counter| Message::Size(counter.value() as u8)); - footer.fixed(&crate::dial(self.spinner as f64), HEIGHT); - Frame::default(); - crate::button("Save as...", "@#filesaveas", &mut footer).on_event(move |_| Message::Save); - footer.end(); - - page.end(); { + let mut header = Flex::default(); + crate::menu(&mut header); + Frame::default(); + crate::choice("From", &self.lang.join("|"), self.from, &mut header) + .on_event(move |choice| Message::From(choice.value() as u8)); + crate::button("Switch", "@#refresh", &mut header).on_event(move |_| Message::Switch); + crate::choice("To", &self.lang.join("|"), self.to, &mut header) + .on_event(move |choice| Message::To(choice.value() as u8)); + Frame::default(); + let mut button = + crate::button("Speak", "@#<", &mut header).with_type(ButtonType::Toggle); + button.set(self.speak); + button.on_event(move |button| Message::Speak(button.value())); + header.end(); header.set_pad(0); + page.fixed(&header, HEIGHT); + } + { + let mut hero = Flex::default_fill().column().with_id("HERO"); + crate::text("Source", &self.source, self.font, self.size) + .on_event(move |text| Message::Source(text.buffer().unwrap().text())); + hero.fixed(&Frame::default(), PAD); + crate::text("Target", &self.target, self.font, self.size); + hero.end(); hero.set_pad(0); hero.fixed(&hero.child(1).unwrap(), PAD); - hero.handle(move |flex, event| { - if event == Event::Resize { - flex.set_type( - match flex.width() < flex.height() { - true => FlexType::Column, - false => FlexType::Row, - } - ); - flex.fixed(&flex.child(0).unwrap(), 0); - flex.fixed(&flex.child(1).unwrap(), PAD); - true - } else { - false - } - }); + } + { + let mut footer = Flex::default(); //FOOTER + crate::button("Open...", "@#fileopen", &mut footer).on_event(move |_| Message::Open); + Frame::default(); + crate::choice("Font", &app::fonts().join("|"), self.font, &mut footer) + .on_event(move |choice| Message::Font(choice.value() as u8)); + crate::button("Translate", "@#circle", &mut footer) + .on_event(move |_| Message::Translate); + crate::counter("Size", self.size as f64, &mut footer) + .with_type(CounterType::Simple) + .on_event(move |counter| Message::Size(counter.value() as u8)); + crate::dial(&mut footer); + Frame::default(); + crate::button("Save as...", "@#filesaveas", &mut footer) + .on_event(move |_| Message::Save); + footer.end(); footer.set_pad(0); - page.fixed(&header, HEIGHT); page.fixed(&footer, HEIGHT); + } + page.end(); + { page.set_margin(PAD); page.set_pad(PAD); page.set_frame(FrameType::FlatBox); @@ -220,12 +169,6 @@ impl Sandbox for Model { "Translate from {} to {} - {NAME}", self.lang[self.from as usize], self.lang[self.to as usize] )); - window.size_range( - DEFAULT[0] as i32 * U8 + DEFAULT[1] as i32, - DEFAULT[0] as i32 * U8 + DEFAULT[1] as i32, - 0, - 0, - ); } } @@ -313,19 +256,19 @@ impl Model { button.activate(); } fn quit(&self) { - let file = env::var("HOME").unwrap() + PATH + NAME; + let file = app::GlobalState::::get().with(move |model| model.clone()); let window = app::first_window().unwrap(); fs::write( file, [ - (window.width() / U8) as u8, - (window.width() % U8) as u8, - (window.height() / U8) as u8, - (window.height() % U8) as u8, - self.from, - self.to, - self.font, - self.size, + (window.width() / U8) as u8, //[0] + (window.width() % U8) as u8, //[1] + (window.height() / U8) as u8, //[2] + (window.height() % U8) as u8, //[3] + self.from, //[4] + self.to, //[5] + self.font, //[6] + self.size, //[7] ], ) .unwrap(); @@ -351,11 +294,12 @@ fn counter(tooltip: &str, value: f64, flex: &mut Flex) -> Counter { element } -fn dial(value: f64) -> Dial { +fn dial(flex: &mut Flex) { + const DIAL: u8 = 120; let mut element = Dial::default().with_id("SPINNER"); element.deactivate(); element.set_maximum((DIAL / 4 * 3) as f64); - element.set_value(value); + element.set_value(element.minimum()); element.set_callback(move |dial| { dial.set_value(if dial.value() == (DIAL - 1) as f64 { dial.minimum() @@ -363,7 +307,7 @@ fn dial(value: f64) -> Dial { dial.value() + 1f64 }) }); - element + flex.fixed(&element, HEIGHT); } fn choice(tooltip: &str, choice: &str, value: u8, flex: &mut Flex) -> Choice { @@ -389,9 +333,10 @@ fn text(tooltip: &str, value: &str, font: u8, size: u8) -> TextEditor { element } -fn menu() -> MenuButton { +fn menu(flex: &mut Flex) { let element = MenuButton::default(); - element.clone() + element + .clone() .on_item_event( "@#circle T&ranslate", Shortcut::Ctrl | 'r', @@ -410,7 +355,7 @@ fn menu() -> MenuButton { MenuFlag::Normal, move |_| Message::Quit, ); - element + flex.fixed(&element, HEIGHT); } fn info() { @@ -511,10 +456,9 @@ pub fn once() -> bool { const NAME: &str = "FlDialect"; const PATH: &str = "/.config"; -const DIAL: u8 = 120; const PAD: i32 = 10; const HEIGHT: i32 = PAD * 3; -const WIDTH: i32 = 125; +const WIDTH: i32 = 125; const U8: i32 = 255; const DEFAULT: [u8; 9] = [ 1, // [0] window_width * U8 + diff --git a/examples/flglyph.rs b/examples/flglyph.rs new file mode 100644 index 0000000..f586252 --- /dev/null +++ b/examples/flglyph.rs @@ -0,0 +1,139 @@ +#![forbid(unsafe_code)] + +use flemish::{ + app, + button::Button, + color_themes, + enums::{FrameType, Font}, + frame::Frame, + browser::{Browser, BrowserType}, + group::Flex, + prelude::*, + OnEvent, Sandbox, Settings, +}; + +#[derive(Debug, Clone)] +struct Model { + list: Vec, + curr: usize, +} + +impl Model { + fn init() -> Self { + Self { + list: (0x2700..=0x27BF).map(|x| char::from_u32(x).unwrap()).collect(), + curr: 0, + } + } + fn choice(&mut self, curr: usize) { + self.curr = curr; + } + pub fn inc(&mut self) { + if !self.list.is_empty() { + match self.curr < self.list.len() - 1 { + true => self.curr += 1, + false => self.curr = 0, + }; + } + } + pub fn dec(&mut self) { + if !self.list.is_empty() { + match self.curr > 0 { + true => self.curr -= 1, + false => self.curr = self.list.len() - 1, + }; + } + } +} + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +const PAD: i32 = 10; +const HEIGHT: i32 = PAD * 3; +const WIDTH: i32 = HEIGHT * 3; + +#[derive(Clone, Copy)] +enum Message { + Inc, + Dec, + Choice(usize) +} + +impl Sandbox for Model { + type Message = Message; + + fn new() -> Self { + Model::init() + } + + fn title(&self) -> String { + String::from("FlPictures") + } + + fn view(&mut self) { + let mut page = Flex::default_fill(); + { + let mut left = Flex::default_fill().column(); + crate::browser("List", self.clone()).on_event(move |browser| { + Message::Choice((browser.value() as usize).saturating_sub(1)) + }); + let mut buttons = Flex::default(); + Button::default().with_label("@#<").on_event(move |_| Message::Dec); + Button::default().with_label("@#>").on_event(move |_| Message::Inc); + buttons.end(); + buttons.set_pad(0); + left.end(); + left.set_pad(PAD); + left.fixed(&buttons, HEIGHT); + page.fixed(&left, WIDTH); + crate::frame("Canvas", self.clone()); + } + page.end(); + page.set_pad(PAD); + page.set_margin(PAD); + page.set_frame(FrameType::FlatBox); + } + + fn update(&mut self, message: Message) { + match message { + Message::Choice(value) => self.choice(value), + Message::Dec => self.dec(), + Message::Inc => self.inc(), + } + } +} + +fn browser(tooltip: &str, value: Model) -> Browser { + let mut element = Browser::default().with_type(BrowserType::Hold); + element.set_tooltip(tooltip); + element.set_label_font(Font::Zapfdingbats); + element.set_text_size(16); + if !value.list.is_empty() { + for item in value.list { + element.add(&item.to_string()); + } + element.select(value.curr as i32 + 1); + } + element +} + +fn frame(tooltip: &str, value: Model) -> Frame { + let mut element = Frame::default(); + element.set_frame(FrameType::DownBox); + element.set_label_font(Font::Zapfdingbats); + element.set_label_size(250); + element.set_tooltip(tooltip); + if !value.list.is_empty() { + element.set_label(&value.list[value.curr].to_string()); + }; + element +} diff --git a/examples/flnetport.rs b/examples/flnetport.rs index 2761590..081538e 100644 --- a/examples/flnetport.rs +++ b/examples/flnetport.rs @@ -8,7 +8,7 @@ use { enums::FrameType, frame::Frame, group::Flex, - input::IntInput, + input::{Input, InputType}, prelude::*, valuator::{Counter, CounterType}, OnEvent, Sandbox, Settings, @@ -32,13 +32,6 @@ pub fn main() { }) } -#[derive(Clone)] -struct Model { - status: String, - address: [u8; 4], - port: u32, -} - #[derive(Clone, Copy)] enum Message { Octet(usize, u8), @@ -46,6 +39,13 @@ enum Message { Check, } +#[derive(Clone)] +struct Model { + status: String, + address: [u8; 4], + port: u32, +} + impl Sandbox for Model { type Message = Message; @@ -58,47 +58,43 @@ impl Sandbox for Model { } fn title(&self) -> String { - String::from("FlNetPort") + format!( + "[{}] {:?}:{} - FlNetPort", + self.status, self.address, self.port + ) } fn view(&mut self) { let mut page = Flex::default_fill().column(); - let mut header = Flex::default(); - header.fixed(&Frame::default().with_label("IP address:"), WIDTH); - for idx in 0..4 { - let mut octet = crate::counter(&mut header); - octet.set_value(self.address[idx] as f64); - octet - .clone() - .on_event(move |_| Message::Octet(idx, octet.value() as u8)); - } - Frame::default(); - let mut port = IntInput::default().with_label("Port:"); - port.set_value(&self.port.to_string()); - header.fixed(&port, WIDTH); - port.clone() - .on_event(move |_| Message::Port(port.value().parse::().unwrap())); - header.end(); - let mut hero = Flex::default(); - Frame::default().with_label(&self.status); - hero.end(); - let mut footer = Flex::default(); - Button::default() - .with_label("Check") - .on_event(move |_| Message::Check); - footer.end(); - page.end(); { - header.set_frame(FrameType::DownFrame); + let mut header = Flex::default(); + header.fixed(&Frame::default().with_label("IP address:"), WIDTH); + for idx in 0..4 { + crate::counter(self.address[idx] as f64, &mut header) + .on_event(move |octet| Message::Octet(idx, octet.value() as u8)); + } + Frame::default(); + crate::input(&self.port.to_string(), &mut header) + .with_label("Port:") + .on_event(move |input| Message::Port(input.value().parse::().unwrap())); + header.fixed( + &Button::default() + .with_label("@#->") + .clone() + .on_event(move |_| Message::Check), + HEIGHT, + ); + header.end(); header.set_pad(0); - hero.set_frame(FrameType::DownFrame); - footer.set_frame(FrameType::DownFrame); - page.set_pad(PAD); - page.set_margin(PAD); - page.set_frame(FrameType::FlatBox); page.fixed(&header, HEIGHT); - page.fixed(&footer, HEIGHT); } + Frame::default() + .with_label(&self.status) + .set_frame(FrameType::DownFrame); + page.end(); + page.set_pad(PAD); + page.set_margin(PAD); + page.set_frame(FrameType::FlatBox); } fn update(&mut self, message: Message) { @@ -144,10 +140,18 @@ impl Sandbox for Model { } } -fn counter(flex: &mut Flex) -> Counter { +fn counter(value: f64, flex: &mut Flex) -> Counter { let mut element = Counter::default().with_type(CounterType::Simple); - element.set_range(0_f64, 254_f64); + element.set_maximum(254_f64); element.set_precision(0); + element.set_value(value); + flex.fixed(&element, WIDTH); + element +} + +fn input(value: &str, flex: &mut Flex) -> Input { + let mut element = Input::default().with_type(InputType::Int); + element.set_value(value); flex.fixed(&element, WIDTH); element } diff --git a/examples/flpicture.rs b/examples/flpicture.rs index 65b5653..21c4f01 100644 --- a/examples/flpicture.rs +++ b/examples/flpicture.rs @@ -11,15 +11,14 @@ use flemish::{ image::SharedImage, menu::{MenuButton, MenuFlag}, prelude::*, - valuator::{Slider, SliderType}, OnEvent, OnMenuEvent, Sandbox, Settings, }; use std::fs; pub fn main() { Model::new().run(Settings { - size: (640, 480), - resizable: true, + size: (640, 360), + resizable: false, ignore_esc_close: true, color_map: Some(color_themes::DARK_THEME), scheme: Some(app::Scheme::Base), @@ -30,15 +29,16 @@ pub fn main() { const PAD: i32 = 10; const HEIGHT: i32 = PAD * 3; +#[derive(Debug, Clone)] struct Image { file: String, image: SharedImage, } +#[derive(Debug, Clone)] struct Model { list: Vec, - size: f64, - current: usize, + curr: usize, } #[derive(Clone, Copy)] @@ -56,8 +56,7 @@ impl Sandbox for Model { fn new() -> Self { Self { list: Vec::new(), - size: 1f64, - current: 0, + curr: 0, } } @@ -67,45 +66,24 @@ impl Sandbox for Model { fn view(&mut self) { let mut page = Flex::default_fill().column(); - let mut header = Flex::default(); - crate::menu(&mut header); - crate::button("Open", "@#fileopen", &mut header).on_event(|_| Message::Open); - crate::button("Prev", "@#|<", &mut header).on_event(|_| Message::Prev); - let mut size = crate::slider("Size").with_type(SliderType::Horizontal); - crate::button("Next", "@#>|", &mut header).on_event(|_| Message::Next); - crate::button("Remove", "@#1+", &mut header).on_event(|_| Message::Remove); - header.end(); - let mut hero = Flex::default_fill(); - let mut frame = crate::frame("Image").with_id("image-frame"); - hero.end(); - page.end(); { + let mut header = Flex::default(); + crate::menu(&mut header); + crate::button("Open", "@#fileopen", &mut header).on_event(|_| Message::Open); + crate::button("Prev", "@#|<", &mut header).on_event(|_| Message::Prev); + Frame::default(); + crate::button("Next", "@#>|", &mut header).on_event(|_| Message::Next); + crate::button("Remove", "@#1+", &mut header).on_event(|_| Message::Remove); + header.end(); header.set_pad(0); header.set_margin(0); - header.set_frame(FrameType::DownBox); - hero.set_margin(0); - hero.set_frame(FrameType::DownBox); - page.set_frame(FrameType::FlatBox); - page.set_pad(PAD); - page.set_margin(PAD); page.fixed(&header, HEIGHT); + crate::frame("Image", self.clone()); } - - let image = if self.list.is_empty() { - None:: - } else { - let mut image = self.list[self.current].image.clone(); - image.scale( - (frame.w() as f64 * self.size) as i32, - (frame.h() as f64 * self.size) as i32, - true, - true, - ); - Some(image) - }; - - frame.set_image(image.clone()); - size.set_callback(move |s| slider_cb(s, image.clone())); + page.end(); + page.set_pad(PAD); + page.set_margin(PAD); + page.set_frame(FrameType::FlatBox); } fn update(&mut self, message: Message) { @@ -139,14 +117,14 @@ impl Model { }; }; } - self.current = 0; + self.curr = 0; }; } fn prev(&mut self) { if !self.list.is_empty() { - self.current = match self.current > 0 { - true => self.current.saturating_sub(1), + self.curr = match self.curr > 0 { + true => self.curr.saturating_sub(1), false => self.list.len() - 1, }; } @@ -154,8 +132,8 @@ impl Model { fn next(&mut self) { if !self.list.is_empty() { - self.current = match self.current < self.list.len() - 1 { - true => self.current.saturating_add(1), + self.curr = match self.curr < self.list.len() - 1 { + true => self.curr.saturating_add(1), false => 0, }; } @@ -165,11 +143,11 @@ impl Model { if !self.list.is_empty() { match choice2_default("Remove ...?", "Remove", "Cancel", "Permanent") { Some(0) => { - self.list.remove(self.current); + self.list.remove(self.curr); } Some(2) => { - if fs::remove_file(self.list[self.current].file.clone()).is_ok() { - self.list.remove(self.current); + if fs::remove_file(self.list[self.curr].file.clone()).is_ok() { + self.list.remove(self.curr); } } _ => {} @@ -186,10 +164,14 @@ fn button(tooltip: &str, label: &str, flex: &mut Flex) -> Button { element } -fn frame(tooltip: &str) -> Frame { - let mut element = Frame::default_fill(); +fn frame(tooltip: &str, value: Model) -> Frame { + let mut element = Frame::default(); element.set_tooltip(tooltip); - element.set_image(None::); + element.set_image(match value.list.is_empty() { + true => None::, + false => Some(value.list[value.curr].image.clone()), + }); + element.set_frame(FrameType::DownBox); element } @@ -229,24 +211,3 @@ fn menu(flex: &mut Flex) { |_| Message::Quit, ); } - -fn slider(tooltip: &str) -> Slider { - let mut element = Slider::default(); - element.set_tooltip(tooltip); - element.set_value(element.maximum()); - element -} - -fn slider_cb(s: &mut Slider, image: Option) { - let mut frame: Frame = app::widget_from_id("image-frame").unwrap(); - if let Some(mut image) = image.clone() { - image.scale( - (frame.width() as f64 * s.value()) as i32, - (frame.height() as f64 * s.value()) as i32, - true, - true, - ); - frame.set_image(Some(image)); - app::redraw(); - } -} diff --git a/examples/flresters.rs b/examples/flresters.rs index 5719c1a..3ac7b5f 100644 --- a/examples/flresters.rs +++ b/examples/flresters.rs @@ -7,8 +7,8 @@ use { color_themes, enums::{Align, Color, Font, FrameType}, frame::Frame, + misc::InputChoice, group::Flex, - input::Input, menu::Choice, prelude::*, text::{StyleTableEntry, TextBuffer, TextDisplay, WrapMode}, @@ -62,10 +62,11 @@ impl Sandbox for Model { fn view(&mut self) { let mut page = Flex::default_fill().column(); let mut header = Flex::default(); - crate::choice(self.method as i32, &mut header) + header.fixed(&Frame::default(), WIDTH); + crate::choice(self.method as i32, &mut header).with_label("Method: ") .on_event(move |choice| Message::Method(choice.value() as u8)); - header.fixed(&Frame::default().with_label("https://"), WIDTH); - crate::input(&self.url).on_event(move |input| Message::Url(input.value())); + header.fixed(&Frame::default(), WIDTH); + crate::input(&self.url).on_event(move |input| Message::Url(input.value().unwrap())); crate::button(&mut header).on_event(move |_| Message::Request); header.end(); crate::text(&self.responce); @@ -145,8 +146,13 @@ fn button(flex: &mut Flex) -> Button { element } -fn input(value: &str) -> Input { - let mut element = Input::default(); +fn input(value: &str) -> InputChoice { + let mut element = InputChoice::default().with_label("URL: "); + for item in ["users", "posts", "albums", "todos", "comments", "posts"] { + element.add(&(format!(r#"https:\/\/jsonplaceholder.typicode.com\/{item}"#))); + } + element.add(r#"https:\/\/lingva.ml\/api\/v1\/languages"#); + element.add(r#"https:\/\/ipinfo.io\/json"#); element.set_value(value); element } diff --git a/examples/input.rs b/examples/input.rs deleted file mode 100644 index f0aaded..0000000 --- a/examples/input.rs +++ /dev/null @@ -1,56 +0,0 @@ -use flemish::{ - app, button::Button, color_themes, frame::Frame, group::Flex, input::Input, prelude::*, - OnEvent, Sandbox, Settings, -}; - -pub fn main() { - State::new().run(Settings { - size: (300, 100), - resizable: true, - color_map: Some(color_themes::DARK_THEME), - scheme: Some(app::Scheme::Base), - ..Default::default() - }) -} - -#[derive(Default)] -struct State { - text: String, -} - -#[derive(Debug, Clone)] -enum Message { - Submit(String), -} - -impl Sandbox for State { - type Message = Message; - - fn new() -> Self { - Self::default() - } - - fn title(&self) -> String { - String::from("State - fltk-rs") - } - - fn update(&mut self, message: Message) { - match message { - Message::Submit(value) => { - self.text = value.clone(); - println!("Hello {}", value); - } - } - } - - fn view(&mut self) { - let col = Flex::default_fill().column(); - Frame::default().with_label("Enter name:"); - let mut name = Input::default(); - name.set_value(&self.text); - Button::default() - .with_label("Submit") - .on_event(move |_| Message::Submit(name.value())); - col.end(); - } -} diff --git a/examples/inputchoice.rs b/examples/inputchoice.rs new file mode 100644 index 0000000..98befdb --- /dev/null +++ b/examples/inputchoice.rs @@ -0,0 +1,111 @@ +#![forbid(unsafe_code)] + +use flemish::{ + app, color_themes, frame::Frame, group::Flex, misc::InputChoice, prelude::*, OnEvent, Sandbox, + Settings, +}; + +pub fn main() { + Model::new().run(Settings { + size: (360, 640), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +const PAD: i32 = 10; + +#[derive(Clone)] +enum Message { + Selected(String), +} + +struct Model { + language: String, + text: String, +} + +impl Sandbox for Model { + fn new() -> Self { + Self { + language: String::new(), + text: String::new(), + } + } + + fn view(&mut self) { + let mut page = Flex::default_fill().column(); + { + Frame::default().with_label("What is your language?"); + crate::input(&self.language); + Frame::default(); + Frame::default().with_label(&self.text); + } + page.end(); + page.fixed(&page.child(0).unwrap(), 30); + page.fixed(&page.child(1).unwrap(), 30); + page.fixed(&page.child(3).unwrap(), 30); + page.set_margin(PAD); + } + + type Message = Message; + fn update(&mut self, message: Message) { + match message { + Message::Selected(value) => { + self.language = value.clone(); + self.text = crate::hello(&value).to_string(); + } + } + } + + fn title(&self) -> String { + format!("InputChoice -{}- Flemish", self.language) + } +} + +const LANGUAGES: [&str; 8] = [ + "Danish", + "English", + "French", + "German", + "Italian", + "Portuguese", + "Spanish", + "Other", +]; + +fn hello(label: &str) -> &str { + match label { + "Danish" => "Halloy!", + "English" => "Hello!", + "French" => "Salut!", + "German" => "Hallo!", + "Italian" => "Ciao!", + "Portuguese" => "Olá!", + "Spanish" => "¡Hola!", + _ => "... hello?", + } +} + +fn input(value: &str) { + let mut element = InputChoice::default(); + element.input().set_value(value); + let mut choice = element.clone(); + element.input().set_callback(move |input| { + choice.clear(); + for lang in crate::LANGUAGES { + if lang + .to_lowercase() + .starts_with(&input.value().to_lowercase()) + { + choice.add(lang); + } + } + }); + element.input().do_callback(); + element.set_value_index(0); + element.on_event(move |choice| Message::Selected(choice.value().unwrap())); +} diff --git a/examples/menu.rs b/examples/menu.rs index c770fb0..ceaf7c1 100644 --- a/examples/menu.rs +++ b/examples/menu.rs @@ -9,8 +9,8 @@ use flemish::{ }; pub fn main() { - MenuApp::new().run(Settings { - size: (300, 300), + Model::new().run(Settings { + size: (640, 360), resizable: true, ignore_esc_close: true, color_map: Some(color_themes::DARK_THEME), @@ -20,7 +20,7 @@ pub fn main() { } #[derive(Default)] -struct MenuApp { +struct Model { value: i32, } @@ -30,7 +30,7 @@ enum Message { DecrementPressed, } -impl Sandbox for MenuApp { +impl Sandbox for Model { type Message = Message; fn new() -> Self { diff --git a/examples/progress.rs b/examples/progress.rs new file mode 100644 index 0000000..80761a3 --- /dev/null +++ b/examples/progress.rs @@ -0,0 +1,79 @@ +#![forbid(unsafe_code)] + +use flemish::{ + app, color_themes, + group::Flex, + misc::Progress, + prelude::*, + valuator::{Slider, SliderType}, + OnEvent, Sandbox, Settings, +}; + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +const PAD: i32 = 10; + +#[derive(Clone)] +enum Message { + Slider(f64), +} + +struct Model { + value: f64, +} + +impl Sandbox for Model { + type Message = Message; + + fn title(&self) -> String { + String::from("Progress - Flemish") + } + + fn new() -> Self { + Self { value: 0f64 } + } + + fn view(&mut self) { + let mut page = Flex::default_fill() + .column() + .with_size(560, 70) + .center_of_parent(); + { + crate::progress(self.value); + crate::slider(self.value).on_event(move |slider| Message::Slider(slider.value())); + } + page.end(); + page.set_pad(PAD); + } + + fn update(&mut self, message: Message) { + match message { + Message::SliderChanged(value) => self.value = value, + } + } +} + +const MAX: f64 = 100f64; + +fn progress(value: f64) { + let mut element = Progress::default(); + element.set_maximum(MAX); + element.set_value(value); +} + +fn slider(value: f64) -> Slider { + let mut element = Slider::default().with_type(SliderType::Horizontal); + element.set_maximum(MAX); + element.set_precision(0); + element.set_value(value); + element +} diff --git a/examples/temperature.rs b/examples/temperature.rs new file mode 100644 index 0000000..cb56d54 --- /dev/null +++ b/examples/temperature.rs @@ -0,0 +1,94 @@ +use flemish::{ + app, color_themes, + enums::{CallbackTrigger, FrameType}, + frame::Frame, + group::Flex, + input::{Input, InputType}, + prelude::*, + OnEvent, Sandbox, Settings, +}; + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +#[derive(Debug, Clone)] +enum Message { + Celsius(i32), + Fahrenheit(i32), +} + +const WIDTH: i32 = 90; + +struct Model { + celsius: i32, + fahrenheit: i32, +} + +impl Model { + fn celsius(&mut self, value: i32) { + self.fahrenheit = value; + self.celsius = ((value as f64 - 32f64) * (5f64 / 9f64)).round() as i32 + } + fn fahrenheit(&mut self, value: i32) { + self.celsius = value; + self.fahrenheit = (value as f64 * (9f64 / 5f64) + 32f64).round() as i32 + } +} + +impl Sandbox for Model { + type Message = Message; + + fn new() -> Self { + Self { + celsius: 0, + fahrenheit: 32, + } + } + + fn title(&self) -> String { + format!("{} : {} - 7GUI: Temperature", self.celsius, self.fahrenheit) + } + + fn view(&mut self) { + let mut page = Flex::default() + .with_size(WIDTH * 3, WIDTH) + .center_of_parent(); + { + Frame::default(); + let mut right = Flex::default().column(); + crate::input(self.celsius) + .with_label("Celsius: ") + .on_event(move |input| Message::Celsius(input.value().parse::().unwrap())); + crate::input(self.fahrenheit) + .with_label("Fahrenheit: ") + .on_event(move |input| Message::Fahrenheit(input.value().parse::().unwrap())); + right.end(); + right.set_pad(10); + } + page.end(); + page.set_pad(0); + page.set_margin(10); + page.set_frame(FrameType::UpBox); + } + + fn update(&mut self, message: Message) { + match message { + Message::Celsius(value) => self.fahrenheit(value), + Message::Fahrenheit(value) => self.celsius(value), + } + } +} + +fn input(value: i32) -> Input { + let mut element = Input::default().with_type(InputType::Int); + element.set_value(&value.to_string()); + element +}