From 58239e97ed2372c5110db5684a4c228bcb5ebf95 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
token.tile = pen
Specifies a pen to paint as one tile before the main part of the token.
token.width = ...
+If specified either as a value or a callback, the text field is padded +or truncated to the specified number.
+token.pad_char = '?'
+If specified together with width, the padding area is filled with +this character instead of just being skipped over.
+token.key = '...'
Specifies the keycode associated with the token. The string description of the key binding is added to the text content of the token.
@@ -2848,7 +2856,9 @@ this may be extended with mouse click support.Pressing 'c' switches the current constraint between counting stacks or items. -Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the -bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting -items and expanding the range each gives a 5x bonus).
-Pressing 'n' produces a list of possible outputs of this job as guessed by +
Pressing 'I' switches the current constraint between counting stacks or items. +Pressing 'R' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the +bounds by 5, 10, or 20 depending on the direction and the 'I' setting (counting +items and expanding the range each gives a 2x bonus).
+Pressing 'A' produces a list of possible outputs of this job as guessed by workflow, and lets you create a new constraint by choosing one as template. If you don't see the choice you want in the list, it likely means you have to adjust the job material first using job item-material or gui/workshop-job, @@ -3050,6 +3050,23 @@ added to the list. If you use Shift-Enter, the interface proceeds to the next dialog, which allows you to edit the suggested constraint parameters to suit your need, and set the item count range.
Pressing 'S' (or, with the example config, Alt-W in the 'z' stocks screen) +opens the overall status screen, which was copied from the C++ implementation +by falconne for better integration with the rest of the lua script:
+This screen shows all currently existing workflow constraints, and allows +monitoring and/or changing them from one screen. The constraint list can +be filtered by typing text in the field below.
+The color of the stock level number indicates how "healthy" the stock level +is, based on current count and trend. Bright green is very good, green is good, +red is bad, bright red is very bad.
+The limit number is also color-coded. Red means that there are currently no +workshops producing that item (i.e. no jobs). If it's yellow, that means the +production has been delayed, possibly due to lack of input materials.
+The chart on the right is a plot of the last 14 days (28 half day plots) worth +of stock history for the selected item, with the rightmost point representing +the current stock value. The bright green dashed line is the target +limit (maximum) and the dark green line is that minus the gap (minimum).
8A$jMxyyhd{EV5C^H10$kI^!-QE7h7y-0Ek8{$=H#b2!{ z@p3dPVw#n1U!cyfZ4}-JJ;>1Bg;^Eqj!3KCA0-{{xA`&Zwg5)l#b!_d^*ActFVC`% zOd(i%`!qeXMxfK>gg6yG209BbpyQckm+1o`-vY%UA9h$lZ1QeO6BX2I-@m=Gaa7dg z{NV%C4>yz5Nw?Y><%#9gBvdMFXJBZ!T)CqyLy6?5=5N$y^aS;J_QCC5CNXXGp10>e zbsrNSG2i%;iu(2*W+U0g@N^2C@d(3K-cVK~HRa-6+`8sH>nt5@p||m{BJ*R%V)gB? z*7n79svfIX)lQ>Nfn=Uxb($sVsz-O}GfD6|QQc3ytI`7(lu%-=AAXB5oXO1Z*1FHv zw#yYBS5VU6a;Qf8p0u)n$t}4H6$x`bEGzlg^3E{@{bO)JBJ3RTc<{0nj#4(~ zSE)~}zeFJRv03?Qm<8_VLI2gK+#b8G;FP&jwJ9d;G?K=Q9R#KRMcyhVDf^M=Bhrfg zLn@^g3q)DkzG=PkTsISz!>S%hyNHq^+)_fx@_nj1YU_57MWw$#Npk=0L^0Q25>J0V z|NCd{uZ1sP&M`P*5xWIVZsm+Za9Y`2p|w5k+QXe!x%u{0WX?H_!Y>7To*Hn*={~8p zLEz!zB;9-=PKuW_#TwN`|Dm(vOCx^Uwd3uIuJ4MqPi8(j>o3XG+im|#a*r&nBNv4I z`YYe Ome_*~d(nVxaVL;a(^Qhv6c zExzcSE-jfE$51NuF>`k;5Unnw_>o4t1$tm*m7IZ-lsLc2J7;tECx==&VR_55Y0mbI z(3^bNUJfrb5{jtX_u*EVk^Fj2Kb14lqaf 8EGn^?Gy-rn?3fa-&;Rny2Cq ajpY3KS~$wNykYsqO&Jp3JP&Hy$l?b!@F|Iu9he z^&K2^$*?cAoy3(?Rj1iGDK|TvRe&aUogCp~Rg#xZya!$szu9z1_-AMyDei@<%N*kd z%&KZ>%#<;{E%9J#a$XrMGU*+Dv-_>F;7vQHNVi*z$Rx#69=7hA5iVw@q+9v%i+kF!|FZoG{1O7&@nZCWZ~|HYmYByuV 9E z(+@d*mUffaY%c^W1LKuP4o`cMQgY^tf)LD&a0Ruw8XpZz*6Sx1s;dkKeO+V_0>tFS z(fw306@91P=d3LxVGnrD#8f=qzg#UIR=!mh{C>*SUr5nkuPreN#&;mv?s&k-@pDZS zJ&?3wnUN*!@|IW8AHb~Qjm9EQtYuK+?y6a1C?fp1<(b_7-0~hPryxCtZ2B8EttE@3 zLx9%#H0_NDfXB4=-c0$u3VzG_x3Mmc@35<~#uvg>qr>92s%9GcrJ6E$S=w04e!^M{ z--@WhUQF1!f f{abgL6>Ed6Z%HjVdh#=w_mmEVNHX9gx)SuH1i1PQ0qxd7xx z 7v=`u~onV=yrl%Ks_kqnU4E^Ha5iy zPobDyiXF(%=8bs!FZ{MlpUtI8kL_-H0D|*LW&VVaZ}85U%zt)ML3zW#qftoESjn _9Gl*Ah!5X%iy@#MsHBCqJrm=w20iF*++@ ze6GM9Dlqi$M(5wk_b2Tr)C8Ze!uBW-rKH+O%bGTpz3|AMA3A;uAKA8*J8&sGB66+a zyn?Gs5kD9s@zO_#aJTvjD&-RHtDGs^V@o=VV}!dDRO~Vz3~W<4j8nXxN5pnCTs+?f z`;*usP4VWUC3y}KDmlptBw3E~kdX?;6$UsytSO}U58O^}VzIBri*PE?$4R*-#?$b@ zb7y+tmY|euk6R72Q-$Zvv5ck(3@$=QAFIgzSUuiG1VM!UzrsHl62ZqjFmH7B`Wz zr&Om;PF~AoQyyn~JcIOl60Pcu=8_8c)+PW<8>Nn`K_YjbgLb!Wj@>uB^3I#L`=e6n z_k(;U+mgK>K1dMzWLXXjJVS=~a(s5eZuN(pqCa1oAkr)H{++9TS18MhHDZ~bWB^=} zSi^}GDoHNrkU4JkOmyr@k2@uV^U@&p8L)b(q9|m-=NGIgfJ^0lnC*seRKnF8$bY*Q zZa4wJ6~yOvc>F-Qj-xkm3 q&Po+wABfK?b9d-oEk-F_DS}pAMF7j8VL#@j-}8Q1=;Tws}w+wKlBsP z?x4+mQ$S0eiQeZfiTT;6+$*{vt+&I>ZTnlgVHnZvKvb7#o1l7Ul_7Xhqc6Vp^>0t) z;V_Rsl1<@m2eD|6oS~oCmfvT|(U7Qjv#8nYfzP9kMs!ZnJrTyy)7?OBfli|&NCpUl zdhe-;BCR$Wo#oQtZ{g+T_>sa*HIutBQ*(W)q@Cz9IQ^}Ol-PjBO`ROrH~_pZb}YFl z#O8}H0h@dFO!=VEPt?QtDEV2l5hc>J1EEP!hqfUbohf!Xk`+6FXM~%5UE3$x2CGLL zGD?#~wP=TbMO7edBAZx}$lL(lWhTeKq~2X8V2Gpj4!>m>8u>;4z}F+1g%vY$H Kf>3;7>Xf9YnXCXiFS&ZOt|?4y=6Fc5u+j9mOIf?EgxT6L z_26~CO=cePYv&KvqI8#0A3r0y*~2}@Y6LkB@389GA$f}bisngS6w^w`%T%mqJA$o? zF$;WC#E$>kgd%=5x#M8zD2s8b89|s>I7X$d*3g3sx5sT|o32Z5xRj*ouk-rISYj|z zGjEN(<76!M?mIgNkR!m6y4|kVt_FZBUv3Ma2z`E+lAtnQC@SNnO&Gk!-t@E<%-y>( zP~@5;#E#4!%xmUW+up~(J}LVEV5* 6f9r&3>Al$Fj%JP!V(fTABH7NnL<(gs3`f4}X!!=hY&Q<30YaUl}9 ^u1ZV_V|~VuhX!gNw#?~6=BhtXR6R)lAq)DnT8;d` zMd?(P-CWVgdgM6d9TS9jyo?zYSXVE+kitWL(V{urEpTtn2?f?=STTm>7jBY#N_e<@ z|Ih;;=ZBdaQ%Lrc3~fW^g#|wpMBL5d90b>j)^(AJ&K(vlMtrIxyYP%Y*fvW=_aN4p zm#Jp)+DP!nb=ro--kv!2c+VJ9YzcXc%Ta(_V!&gu7vSOUitJ7?d~{$pCv#!d1Ug&p zC*$$?*>aHQqEXUmId98_x4x#)G>q~Q+UYzh;#lR3)mv9FwNS~!kKSrD_N0}4Ya==R zDvj=aYdjQkw3N~bkb@tVXkbDXKUfB*mbyb w&F4=EprijR0}6 zU*v{q$H(fT2~ ao3~cFUt@l z<6n054K1IpT*8H5UpT)(kokhVNodImEGFdQ0~d<=jreF8f Dfx-ZAqZk4_z~z9VB>4V;+L%tu zX`L1yorcc01BjR>L}MOQF&_yM1YitCYgt4TU}2eZ{JAG<5Q;)bFYCNLSUA<^L=3$q zEtXksbX%Y*pSDvNy~wVOtKr6D Y0!XZ6PO;yc<+-s7t@WcP(5Ew-EJ$w28 Vclf!xN{;IQn44OeR2X?)`yZC >iAov8Sj(QHCrfYl>)w!H6>E)gZh@mUu1o zl(nMlQuc^erII}@NWFgZey`tOpK~wgy3T#>`*YvdIjJsA`=uq7Bmn@Brqigd03ZSZ z0E&ze5o&rwo=6Bm$Hn20tq@r()}u#{SS(;;V?!ti0bl{Z#{Z+xz9Do1HYzTkvj+gV z5jxfC(79uC*;dtwJ8@LKkOAcz$0lo qs2V7IHXJ&ugrx=Ei4 zS!O!S?e2^_e!JnVQdv@1yl4I!S<6{9{39Z2Y7g$P_Qjick(J~Fc2L3n72e3o&cA4& zhPGW~puEDm4pfzUs!@?)isR*%%QV?(d!miS!A@2R5z9#^P{veGgIzXX LTbOj7n^h{Syj&ZecC6@sdv+ntJek^QmNPl7F{6)>VF<{-Z??hUR~#Px?z3 z9;{sX!ZTVw=NHJxCE-xz7yR5 ^Vyk9G&@!F6# z)G}Al68_SJ(D^uzc9*U+De$bC%=J9y#eBLs^szXt?af~a{bBamfx~`BE1DjfjK$p2 zw8Z@h(+ANKzs1+`)13fo?V`8&GUD5ap#b%vxaXgiv7Cqci{e~B7D5-lD1TkS!t$(H zmA&_9qfe} rOO~I(;uLs;N{ghQ% zz0@z>meC^WqKO{gzYmK}T(D!maC&ZGJ6wB7MxsZwm2E$_@Nq%Y0|}X~R>tP-9*O|c zm32B_J>)Xo++JFHL1NhA3NEVZUxwSAA20ep?wVa9qq^6#D!VG8e7@dE@R(e|fVc*D zDIM>dlNR>fm^I4qU- +gXxDfMDb@SQN0EqLbQ&n z^4ZKDr#OioIcRD1PTQ4(t=?8Bv&We3KVP5Q9eA<*$E0p4r&$di3TAu?xS~%gIyA0N z79q7ph@q#JJx2fhV~l>}_#z5b6r%d%Wy-L(Vi$&+%CAJEDiE@Ww_*>5md^aoYJboI zqoF& WkQr@4=d m9 zPF M8a-Qv_yS(oDMTNSvSZaH$u^L^MC+ohV#?jcqd`0MD&Q@%037s$z zLpwd7bKehx_qKY1gR<_Msbj<-M;p<^8;yEm-Ky@nxFzCV>j8&svRH~UGBK@fZ)E!6 zoEct2*B(+~m7 A%lw*yoZ!Kac%TBpr?!-;U)F0N3d)n!Sv&@sD+wU}F!-;(?jV }d)pqkN2RDLKi=MkzWX7_MiAn(Dn)*e72X9(rn*5vY`?BhTy-l{;qRGfZ>8lPB9< z$B^Z Q>xmtL^_C=7PABmRe&>x|cOz?~ zy%EqJ2N%R6Uitv4=TaZCESAs!NUe6Ty|Vlil& DjY5eksA&V^ RLpE*bwfO4$I~2rK5#j zzIswSEF&m8`n-|bDs#@gRSw9KvbcJ?D8GgIl_$cgv_}Aq2gydTFo(h!`p#XQk+i^; zHvy(hG^IlP_@cOTa-taTzLwu1&Z|1|_uh?aGQOHdmHRpBG48;{0>cbu?p>{6ttvmd zUfRp0rN0Y8&_e$53Z&Q$8VuL?mPW9 bXq1;G=u(G!^+oX-QG1 zY6N#)E@U!#$B=+&laM~Ly@oEj`#nQI6O?{i9Ldfm_aMo8gmEg{j0sy&r{GI^zt*|l zLV%qHy!X1=VM6qBf}6M3md}p1%_!lIeoq9Ne}A}m!DvfXkY$y )}=947Bom+RjkG6-~1>V0^>^QU*v_-_RWR z5gEl*7Dq)?KS?-%ET@A+)`+13F5y!h5%Rv_P#_|Pk240aUTDz6h?<|5>$7I(E`Fn$ zUAV9{?k026*G60 %{#CE)eO?WNFAnNti(|!y#=xS1W^nRRsH!k!R!L}v`{k2cdt6h5JlZUcz7y{N z*~;^*PwF!IOrBBdHIMWTx wKyi)> zp7IckAn(j!qZm2tg*tw1`C& MYh|9wbB=G znXun@@P2Lo-d?4w$~^&kcup|tj6WKDc}arzK=Yrs{4wQ)4S`ruBtWBbn0kyTRV^|@ z=w Z>8L={LQYkMTi*8`}7!g=x*m8H2YZ9p3}aX zX(oF24vvvpU3;&o&|U~D?N<>#y2r6=+gp0BB+MFEBdxW_ol^<6fP*Ni!B3FLEnrE? z(Fh%CxJZ$^2#0`b_wHP7tkej82=Cs3mK-~8*fehM?pRwfZ^0?4G6W_|7^DwXRzglj zC)hxt+NY}|x+!wB1-_^}1r*dFBK%rrCRdrHQ48>_Q`k!$U!x&L#X@-{277o80^>C= z_Mnsd+E6k|KkAcM(^;F}l6Nq|DQSD|Gkf9~3l@WheIK>2Yv#V10;M@eN0EVRA94g< z7B|P2(O*6XMSeCb)y(c+WrtR;lV4Mj_l|90)t?#HQkf-8hSb(!xt^l1g@mqqR3sqZ z^o_nAsx?~n*P`! }>a2tlRkg+boN$ zG_6S!0EO+-(^wMtWD{G1E&z_ZADSx~0}mr3t9;=Bv;4!Rc7p<^Ej{mnVBcPk!fyhy z8;D&Y9fNS2o@^3da$ptP5gb#_&TNVLJX3ob7d6UP@HENv7&YVjwI24ndWI0!#kfXa zGskgu;H*}LGn_58M-Bp+%lAd5eBI1-nug)XY`w*casL`(T(v|pZSb4-(b8=G>2LXI z1J%oXoVQT}GRbfDv=J`X_}$I>epdRIC3Jly@$&KdUuFBaG{S;!Lg7am!Q1PW&ruf0 zJ!sOX=a{QRQe!-T(yCryA1fKCf|-@8h8jnaDdIo}gYZeMw^b=2 qnW#ebp zh-poyaK7psk$n4<^ymlo4m-81WDq}vwiTGnuZ>?N=Wo XFuU1Q+v&O7eq^=;j{_IA~gNp@|Vy>CwxK@Cpc-44oMg&dRv+=fEX`XNJ z%KnH81;-F7(hAQ_-}xf~{m&>#cb`Qu_w~s23{8@y;M=$Q)fs9GlLs;=Fz;vIf9pv_ z02%0X(a^=UZ)0rue#VEtN Un81}1@gOWKi?0w|= zE0sFlQa1P#&)&P8=)P@CV0Cn$!JJxitTmVbYK(}GxP`=SP)lO%_LviT68|{)=V{o` zs=;Ot9JB~7@B|rTu^%j{Z@f3~|8{V4O{}(JNLVYX^+D+Ah&fkN_*S|tP}un>YAZIH z`z+LWGe^HY@kQp3b7p-2iF*!Iw%pp{A*$HA&c3(V2CMcg0YjoCJtiXhL5Uy}dq&z| zW*G->i1~Oel3+aD?v~FDV_x?8M|0^zilCbLsJVso^x4SHHPFND=c;Me& zF{sD=#BqSjc&B!45#QlSsLd5=e5<~&)Ep^G&jGf-C20-(z@xe>z~`6~^?A^H`0N7j z-wZOVfrv<;8#G_nB&Ih>ajqkwOcXUzn&}0csQ b_e@3Yz4fDbu!YsXGHklXN>Vv6#saak3iZSn?c?5X z;vqNUy-mYhY%VO CdS`>7vm9q2nN1?{JR|zg=x&fa}B-^(o zlM8Omsj9GUN4nl=Fml+C##&+33Qf3}gx@xLQczj6rjs?V_f)(i3#XEpHHIh!g=5W- zO2Koxo3D1tVw8(fJm~0uhEw-sg=~;!W366@QH4}2W`F(GuPxsA>eFU6ZkXYSADoC8 z981H$nyVvlyD2=zFLb8%*kbdK)v4^eLF^0h7viTNyjX3eB(x{DB7w`kakb1CJZx#D zH=6{Z3~Y{qcftFWEAHj3Wfoi+ty_K%vF$HMF!YVgXE2g^g1atyO;W#@nUZ6x$ve(( zGHtKkO5I(qA@SVfIx= BJaahp)4ML*5Da& zNFM9Uoijr!PDgk0Nb;)th3&+*o1T~(6ANM1UdO8xxPWMV`Dc+k>o@#W)z1*>60ms} zFoTvdMd5CInZ$dMc9BsJ3Fs54O)!}ocMzRW`isJ2ji^#j^wQ)&1#{6q0}g9e;nw~b z=(s)nj&+K?#+kZW3wgdV)!5*jP_zsE6e3DT^M3=4rVV1N`H$TH`{<(EI#H{weG>i; DZgqQZ diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index ebbffbbbd..cb9e1c9be 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -418,7 +418,13 @@ List.ATTRS{ function List:init(info) self.page_top = 1 self.page_size = 1 - self:setChoices(info.choices, info.selected) + + if info.choices then + self:setChoices(info.choices, info.selected) + else + self.choices = {} + self.selected = 1 + end end function List:setChoices(choices, selected) @@ -481,6 +487,9 @@ function List:moveCursor(delta, force_cb) if cnt < 1 then self.page_top = 1 self.selected = 1 + if force_cb and self.on_select then + self.on_select(nil,nil) + end return end @@ -657,13 +666,17 @@ function FilteredList:init(info) end end self.not_found = Label{ - visible = false, + visible = true, text = info.not_found_label or 'No matches', text_pen = COLOR_LIGHTRED, frame = { l = info.icon_width, t = self.list.frame.t }, } self:addviews{ self.edit, self.list, self.not_found } - self:setChoices(info.choices, info.selected) + if info.choices then + self:setChoices(info.choices, info.selected) + else + self.choices = {} + end end function FilteredList:getChoices() diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index 9a45f6554..77a87c9ce 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -130,12 +130,20 @@ RangeEditor = defclass(RangeEditor, widgets.Label) RangeEditor.ATTRS { get_cb = DEFAULT_NIL, - save_cb = DEFAULT_NIL + save_cb = DEFAULT_NIL, + keys = { + count = 'CUSTOM_SHIFT_I', + modify = 'CUSTOM_SHIFT_R', + min_dec = 'BUILDING_TRIGGER_MIN_SIZE_DOWN', + min_inc = 'BUILDING_TRIGGER_MIN_SIZE_UP', + max_dec = 'BUILDING_TRIGGER_MAX_SIZE_DOWN', + max_inc = 'BUILDING_TRIGGER_MAX_SIZE_UP', + } } function RangeEditor:init(args) self:setText{ - { key = 'BUILDING_TRIGGER_ENABLE_CREATURE', + { key = self.keys.count, text = function() local cons = self.get_cb() or null_cons if cons.goal_by_count then @@ -145,21 +153,21 @@ function RangeEditor:init(args) end end, on_activate = self:callback('onChangeUnit') }, - { key = 'BUILDING_TRIGGER_ENABLE_MAGMA', text = ': Modify', + { key = self.keys.modify, text = ': Range', on_activate = self:callback('onEditRange') }, NEWLINE, ' ', - { key = 'BUILDING_TRIGGER_MIN_SIZE_DOWN', - on_activate = self:callback('onIncRange', 'goal_gap', 5) }, - { key = 'BUILDING_TRIGGER_MIN_SIZE_UP', + { key = self.keys.min_dec, + on_activate = self:callback('onIncRange', 'goal_gap', 2) }, + { key = self.keys.min_inc, on_activate = self:callback('onIncRange', 'goal_gap', -1) }, { text = function() local cons = self.get_cb() or null_cons return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap) end }, - { key = 'BUILDING_TRIGGER_MAX_SIZE_DOWN', + { key = self.keys.max_dec, on_activate = self:callback('onIncRange', 'goal_value', -1) }, - { key = 'BUILDING_TRIGGER_MAX_SIZE_UP', - on_activate = self:callback('onIncRange', 'goal_value', 5) }, + { key = self.keys.max_inc, + on_activate = self:callback('onIncRange', 'goal_value', 2) }, { text = function() local cons = self.get_cb() or null_cons return string.format(': Max %-4d', cons.goal_value) @@ -200,9 +208,9 @@ end function RangeEditor:onIncRange(field, delta) local cons = self.get_cb() if not cons.goal_by_count then - delta = delta * 5 + delta = delta * 2 end - cons[field] = math.max(1, cons[field] + delta) + cons[field] = math.max(1, cons[field] + delta*5) self.save_cb(cons) end @@ -295,7 +303,7 @@ function NewConstraint:init(args) } }, widgets.Label{ - frame = { l = 0, t = 13 }, + frame = { l = 0, t = 14 }, text = { 'Desired range: ', { pen = COLOR_LIGHTCYAN, @@ -311,7 +319,7 @@ function NewConstraint:init(args) } }, RangeEditor{ - frame = { l = 1, t = 15 }, + frame = { l = 1, t = 16 }, get_cb = self:cb_getfield('constraint'), save_cb = self:callback('onRangeChange'), }, @@ -353,7 +361,7 @@ function NewConstraint:postinit() end function NewConstraint:isValid() - return self.constraint.item_type >= 0 + return self.constraint.item_type >= 0 or self.constraint.is_craft end function NewConstraint:onChange() @@ -455,6 +463,59 @@ function NewConstraint:onRangeChange() cons.goal_gap = math.max(1, math.min(cons.goal_gap, cons.goal_value-1)) end +------------------------------ +-- CONSTRAINT HISTORY GRAPH -- +------------------------------ + +HistoryGraph = defclass(HistoryGraph, widgets.Widget) + +HistoryGraph.ATTRS { + frame_inset = 1, + history_pen = COLOR_CYAN, +} + +function HistoryGraph:init(info) +end + +function HistoryGraph:setData(history, bars) + self.history = history or {} + self.bars = bars or {} + + local maxval = 1 + for i,v in ipairs(self.history) do + maxval = math.max(maxval, v) + end + for i,v in ipairs(self.bars) do + maxval = math.max(maxval, v.value) + end + self.max_value = maxval +end + +function HistoryGraph:onRenderFrame(dc,rect) + dc:fill(rect.x1,rect.y1,rect.x1,rect.y2,{ch='\xb3', fg=COLOR_BROWN}) + dc:fill(rect.x1,rect.y2,rect.x2,rect.y2,{ch='\xc4', fg=COLOR_BROWN}) + dc:seek(rect.x1,rect.y1):char('\x1e', COLOR_BROWN) + dc:seek(rect.x1,rect.y2):char('\xc5', COLOR_BROWN) + dc:seek(rect.x2,rect.y2):char('\x10', COLOR_BROWN) + dc:seek(rect.x1,rect.y2-1):char('0', COLOR_BROWN) +end + +function HistoryGraph:onRenderBody(dc) + local coeff = (dc.height-1)/self.max_value + + for i,v in ipairs(self.bars) do + local y = dc.height-1-math.floor(0.5 + coeff*v.value) + dc:fill(0,y,dc.width-1,y,v.pen or {ch='-', fg=COLOR_GREEN}) + end + + local xbase = dc.width-1-#self.history + for i,v in ipairs(self.history) do + local x = xbase + i + local y = dc.height-1-math.floor(0.5 + coeff*v) + dc:seek(x,y):char('*', self.history_pen) + end +end + ------------------------------ -- GLOBAL CONSTRAINT SCREEN -- ------------------------------ @@ -478,47 +539,7 @@ function ConstraintList:init(args) self:addviews{ widgets.Panel{ - frame = { w = 31, r = 0, h = 6, t = 0 }, - frame_inset = 1, - subviews = { - widgets.Label{ - frame = { l = 0, t = 0 }, - enabled = self:callback('isAnySelected'), - text = { - { text = function() - local cur = self:getCurConstraint() - if cur then - return string.format( - 'Currently %d (%d in use)', - current_stock(cur), - if_by_count(cur, cur.cur_in_use_count, cur.cur_in_use_amount) - ) - else - return 'No constraint selected' - end - end } - } - }, - RangeEditor{ - frame = { l = 0, t = 2 }, - enabled = self:callback('isAnySelected'), - get_cb = self:callback('getCurConstraint'), - save_cb = self:callback('saveConstraint'), - }, - } - }, - widgets.Widget{ - active = false, - frame = { w = 1, r = 31 }, - frame_background = gui.BOUNDARY_FRAME.frame_pen, - }, - widgets.Widget{ - active = false, - frame = { w = 31, r = 0, h = 1, t = 6 }, - frame_background = gui.BOUNDARY_FRAME.frame_pen, - }, - widgets.Panel{ - frame = { l = 0, r = 32 }, + frame = { l = 0, r = 31 }, frame_inset = 1, on_layout = function(body) self.fwidth = body.width - (12+1+1+7+1+1+1+7) @@ -541,6 +562,7 @@ function ConstraintList:init(args) edit_pen = COLOR_LIGHTCYAN, text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, + on_select = self:callback('onSelectConstraint'), }, widgets.Label{ frame = { b = 0, h = 1 }, @@ -557,27 +579,66 @@ function ConstraintList:init(args) else return COLOR_WHITE end - end }, ', ', - { key = 'CUSTOM_SHIFT_S', text = ': Search', - on_activate = function() - self.subviews.list.edit.active = not self.subviews.list.edit.active - end, - pen = function() - if self.subviews.list.edit.active then - return COLOR_LIGHTCYAN + end }, + } + } + } + }, + widgets.Panel{ + frame = { w = 30, r = 0, h = 6, t = 0 }, + frame_inset = 1, + subviews = { + widgets.Label{ + frame = { l = 0, t = 0 }, + enabled = self:callback('isAnySelected'), + text = { + { text = function() + local cur = self:getCurConstraint() + if cur then + return string.format( + 'Currently %d (%d in use)', + current_stock(cur), + if_by_count(cur, cur.cur_in_use_count, cur.cur_in_use_amount) + ) else - return COLOR_WHITE + return 'No constraint selected' end end } } - } + }, + RangeEditor{ + frame = { l = 0, t = 2 }, + enabled = self:callback('isAnySelected'), + get_cb = self:callback('getCurConstraint'), + save_cb = self:callback('saveConstraint'), + keys = { + count = 'CUSTOM_SHIFT_I', + modify = 'CUSTOM_SHIFT_R', + min_dec = 'SECONDSCROLL_PAGEUP', + min_inc = 'SECONDSCROLL_PAGEDOWN', + max_dec = 'SECONDSCROLL_UP', + max_inc = 'SECONDSCROLL_DOWN', + } + }, } }, + widgets.Widget{ + active = false, + frame = { w = 1, r = 30 }, + frame_background = gui.BOUNDARY_FRAME.frame_pen, + }, + widgets.Widget{ + active = false, + frame = { w = 30, r = 0, h = 1, t = 6 }, + frame_background = gui.BOUNDARY_FRAME.frame_pen, + }, + HistoryGraph{ + view_id = 'graph', + frame = { w = 30, r = 0, t = 7, b = 0 }, + } } - self.subviews.list.edit.active = false - - self:initListChoices() + self:initListChoices(nil, args.select_token) end function stock_trend_color(cons) @@ -733,6 +794,29 @@ function ConstraintList:onDeleteConstraint() ) end +function ConstraintList:onSelectConstraint(idx,item) + local history, bars + + if item then + local cons = item.obj + local vfield = if_by_count(cons, 'cur_count', 'cur_amount') + + bars = { + { value = cons.goal_value - cons.goal_gap, pen = {ch='-', fg=COLOR_GREEN} }, + { value = cons.goal_value, pen = {ch='-', fg=COLOR_LIGHTGREEN} }, + } + + history = {} + for i,v in ipairs(cons.history or {}) do + table.insert(history, v[vfield]) + end + + table.insert(history, cons[vfield]) + end + + self.subviews.graph:setData(history, bars) +end + ------------------------------- -- WORKSHOP JOB INFO OVERLAY -- ------------------------------- @@ -772,14 +856,20 @@ function JobConstraints:init(args) widgets.Label{ frame = { l = 0, b = 0 }, text = { - { key = 'CUSTOM_N', text = ': New limit, ', + { key = 'CUSTOM_SHIFT_A', text = ': Add limit, ', on_activate = self:callback('onNewConstraint') }, - { key = 'CUSTOM_X', text = ': Delete', + { key = 'CUSTOM_SHIFT_X', text = ': Delete', enabled = self:callback('isAnySelected'), on_activate = self:callback('onDeleteConstraint') }, NEWLINE, NEWLINE, { key = 'LEAVESCREEN', text = ': Back', - on_activate = self:callback('dismiss') } + on_activate = self:callback('dismiss') }, + ' ', + { key = 'CUSTOM_SHIFT_S', text = ': Status', + on_activate = function() + local sel = self:getCurConstraint() + ConstraintList{ select_token = (sel or {}).token }:show() + end } } }, } @@ -873,22 +963,16 @@ function JobConstraints:onNewConstraint() local choices = {} for i,cons in ipairs(variants) do local itemstr = describe_item_type(cons) - local matstr = describe_material(cons) - local matflags = utils.list_bitfield_flags(cons.mat_mask) - if #matflags > 0 then - local fstr = table.concat(matflags, '/') - if matstr == 'any material' then - matstr = 'any '..fstr - else - matstr = 'any '..fstr..' '..matstr - end + local matstr,matflags = describe_material(cons) + if matflags then + matstr = matflags..' '..matstr end table.insert(choices, { text = itemstr..' of '..matstr, obj = cons }) end dlg.ListBox{ - frame_title = 'New limit', + frame_title = 'Add limit', text = 'Select one of the possible outputs:', text_pen = COLOR_WHITE, choices = choices, @@ -930,7 +1014,7 @@ end local args = {...} -if args[1] == 'list' then +if args[1] == 'status' then check_enabled(function() ConstraintList{}:show() end) else if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then