From d51bc73e89dfc4230a8e5a7be7234372c65ed40d Mon Sep 17 00:00:00 2001 From: Josh Gooderham Date: Sat, 7 Sep 2013 16:12:41 -0400 Subject: [PATCH] - raw-assets and asset building (skin-packing) - basic settings (last image path stored) - basic ui with floating menus - ui components like image picker, layer manager, file dialog - basic layer support - basic image path with thumbnail atlas packing - basic view: LevelStage with atlas packing and layer display - bugs bugs bugs --- .../com/mobidevelop/maps/basic/BasicMap.java | 10 +- .../maps/brush/ImageBrushMapObject.java | 22 ++ maps-editor-android/assets/data/uiskin.atlas | 201 ++++++++------- maps-editor-android/assets/data/uiskin.json | 3 +- maps-editor-android/assets/data/uiskin.png | Bin 22779 -> 22786 bytes maps-editor/.classpath | 19 +- maps-editor/assets-raw/skin/check-off.png | Bin 0 -> 1123 bytes maps-editor/assets-raw/skin/check-on.png | Bin 0 -> 1264 bytes maps-editor/assets-raw/skin/cursor.9.png | Bin 0 -> 86 bytes .../skin/default-pane-noborder.9.png | Bin 0 -> 78 bytes .../assets-raw/skin/default-pane.9.png | Bin 0 -> 93 bytes .../assets-raw/skin/default-rect-down.9.png | Bin 0 -> 93 bytes .../assets-raw/skin/default-rect-pad.9.png | Bin 0 -> 93 bytes .../assets-raw/skin/default-rect.9.png | Bin 0 -> 93 bytes .../assets-raw/skin/default-round-down.9.png | Bin 0 -> 184 bytes .../assets-raw/skin/default-round-large.9.png | Bin 0 -> 404 bytes .../assets-raw/skin/default-round.9.png | Bin 0 -> 296 bytes .../assets-raw/skin/default-scroll.9.png | Bin 0 -> 2850 bytes .../skin/default-select-selection.9.png | Bin 0 -> 85 bytes .../assets-raw/skin/default-select.9.png | Bin 0 -> 423 bytes .../assets-raw/skin/default-slider-knob.png | Bin 0 -> 992 bytes .../assets-raw/skin/default-slider.9.png | Bin 0 -> 103 bytes .../skin/default-splitpane-vertical.9.png | Bin 0 -> 90 bytes .../assets-raw/skin/default-splitpane.9.png | Bin 0 -> 83 bytes .../assets-raw/skin/default-window.9.png | Bin 0 -> 316 bytes maps-editor/assets-raw/skin/default.png | Bin 0 -> 24007 bytes maps-editor/assets-raw/skin/pack.json | 5 + maps-editor/assets-raw/skin/selection.png | Bin 0 -> 922 bytes maps-editor/assets-raw/skin/textfield.9.png | Bin 0 -> 252 bytes maps-editor/assets-raw/skin/tree-minus.png | Bin 0 -> 328 bytes maps-editor/assets-raw/skin/tree-plus.png | Bin 0 -> 336 bytes maps-editor/assets-raw/skin/white.png | Bin 0 -> 181 bytes maps-editor/src/BuildAssets.java | 11 + .../mobidevelop/maps/editor/LevelStage.java | 227 ++++++++++++++++ .../mobidevelop/maps/editor/MapEditor.java | 242 +++++++++++++++++- .../com/mobidevelop/maps/editor/Settings.java | 27 ++ .../maps/editor/commands/MapCommands.java | 23 +- .../editor/commands/MapLayerCommands.java | 82 +++--- .../editor/commands/MapLayersCommands.java | 56 ++-- .../editor/commands/MapObjectCommands.java | 41 +-- .../editor/commands/MapObjectsCommands.java | 48 ++-- .../maps/editor/models/MapModels.java | 186 ++++++++++++-- .../maps/editor/ui/FileDialog.java | 108 ++++++++ .../maps/editor/ui/ImagePicker.java | 115 +++++++++ .../mobidevelop/maps/editor/ui/LayerList.java | 181 +++++++++++++ .../maps/editor/ui/SlideWindow.java | 148 +++++++++++ .../mobidevelop/maps/editor/ui/UIEvents.java | 66 +++++ .../mobidevelop/utils/commands/Command.java | 6 +- .../utils/commands/CommandManager.java | 30 ++- maps/src/com/mobidevelop/maps/Map.java | 1 + 50 files changed, 1619 insertions(+), 239 deletions(-) create mode 100644 maps-basic/src/com/mobidevelop/maps/brush/ImageBrushMapObject.java create mode 100644 maps-editor/assets-raw/skin/check-off.png create mode 100644 maps-editor/assets-raw/skin/check-on.png create mode 100644 maps-editor/assets-raw/skin/cursor.9.png create mode 100644 maps-editor/assets-raw/skin/default-pane-noborder.9.png create mode 100644 maps-editor/assets-raw/skin/default-pane.9.png create mode 100644 maps-editor/assets-raw/skin/default-rect-down.9.png create mode 100644 maps-editor/assets-raw/skin/default-rect-pad.9.png create mode 100644 maps-editor/assets-raw/skin/default-rect.9.png create mode 100644 maps-editor/assets-raw/skin/default-round-down.9.png create mode 100644 maps-editor/assets-raw/skin/default-round-large.9.png create mode 100644 maps-editor/assets-raw/skin/default-round.9.png create mode 100644 maps-editor/assets-raw/skin/default-scroll.9.png create mode 100644 maps-editor/assets-raw/skin/default-select-selection.9.png create mode 100644 maps-editor/assets-raw/skin/default-select.9.png create mode 100644 maps-editor/assets-raw/skin/default-slider-knob.png create mode 100644 maps-editor/assets-raw/skin/default-slider.9.png create mode 100644 maps-editor/assets-raw/skin/default-splitpane-vertical.9.png create mode 100644 maps-editor/assets-raw/skin/default-splitpane.9.png create mode 100644 maps-editor/assets-raw/skin/default-window.9.png create mode 100644 maps-editor/assets-raw/skin/default.png create mode 100644 maps-editor/assets-raw/skin/pack.json create mode 100644 maps-editor/assets-raw/skin/selection.png create mode 100644 maps-editor/assets-raw/skin/textfield.9.png create mode 100644 maps-editor/assets-raw/skin/tree-minus.png create mode 100644 maps-editor/assets-raw/skin/tree-plus.png create mode 100644 maps-editor/assets-raw/skin/white.png create mode 100644 maps-editor/src/BuildAssets.java create mode 100644 maps-editor/src/com/mobidevelop/maps/editor/LevelStage.java create mode 100644 maps-editor/src/com/mobidevelop/maps/editor/Settings.java create mode 100644 maps-editor/src/com/mobidevelop/maps/editor/ui/FileDialog.java create mode 100644 maps-editor/src/com/mobidevelop/maps/editor/ui/ImagePicker.java create mode 100644 maps-editor/src/com/mobidevelop/maps/editor/ui/LayerList.java create mode 100644 maps-editor/src/com/mobidevelop/maps/editor/ui/SlideWindow.java create mode 100644 maps-editor/src/com/mobidevelop/maps/editor/ui/UIEvents.java diff --git a/maps-basic/src/com/mobidevelop/maps/basic/BasicMap.java b/maps-basic/src/com/mobidevelop/maps/basic/BasicMap.java index 1b62775..76ace4e 100644 --- a/maps-basic/src/com/mobidevelop/maps/basic/BasicMap.java +++ b/maps-basic/src/com/mobidevelop/maps/basic/BasicMap.java @@ -1,6 +1,7 @@ package com.mobidevelop.maps.basic; import com.mobidevelop.maps.Map; +import com.mobidevelop.maps.MapLayer; import com.mobidevelop.maps.MapLayers; import com.mobidevelop.maps.MapProperties; import com.mobidevelop.maps.MapResources; @@ -110,5 +111,12 @@ public BasicMap(float x, float y, float width, float height) { public void dispose() { resources.dispose(); } - + + @Override + public MapLayer createLayer( String name ) { + + BasicMapLayer layer = new BasicMapLayer( this ); + layer.setName( name ); + return layer; + } } diff --git a/maps-basic/src/com/mobidevelop/maps/brush/ImageBrushMapObject.java b/maps-basic/src/com/mobidevelop/maps/brush/ImageBrushMapObject.java new file mode 100644 index 0000000..650b465 --- /dev/null +++ b/maps-basic/src/com/mobidevelop/maps/brush/ImageBrushMapObject.java @@ -0,0 +1,22 @@ +package com.mobidevelop.maps.brush; + +import com.mobidevelop.maps.MapLayer; +import com.mobidevelop.maps.basic.BasicMapObject; + + +public class ImageBrushMapObject extends BasicMapObject { + + private String imageName; + + public ImageBrushMapObject( MapLayer layer, float x, float y, String imageName ) { + + super( layer, x, y ); + + this.imageName = imageName; + } + + public String getImageName() { + + return imageName; + } +} diff --git a/maps-editor-android/assets/data/uiskin.atlas b/maps-editor-android/assets/data/uiskin.atlas index c193581..84d7f47 100644 --- a/maps-editor-android/assets/data/uiskin.atlas +++ b/maps-editor-android/assets/data/uiskin.atlas @@ -3,6 +3,35 @@ uiskin.png format: RGBA8888 filter: Nearest,Nearest repeat: none +check-off + rotate: false + xy: 11, 5 + size: 14, 14 + orig: 14, 14 + offset: 0, 0 + index: -1 +textfield + rotate: false + xy: 11, 5 + size: 14, 14 + orig: 14, 14 + offset: 0, 0 + index: -1 +check-on + rotate: false + xy: 125, 35 + size: 14, 14 + orig: 14, 14 + offset: 0, 0 + index: -1 +cursor + rotate: false + xy: 23, 1 + size: 3, 3 + split: 1, 1, 1, 1 + orig: 3, 3 + offset: 0, 0 + index: -1 default rotate: false xy: 1, 50 @@ -10,51 +39,44 @@ default orig: 254, 77 offset: 0, 0 index: -1 -default-window - rotate: false - xy: 1, 20 - size: 27, 29 - split: 4, 3, 20, 3 - orig: 27, 29 - offset: 0, 0 - index: -1 -default-select +default-pane rotate: false - xy: 29, 29 - size: 27, 20 - split: 4, 14, 4, 4 - orig: 27, 20 + xy: 11, 1 + size: 5, 3 + split: 1, 1, 1, 1 + orig: 5, 3 offset: 0, 0 index: -1 -default-round-large +default-rect-pad rotate: false - xy: 57, 29 - size: 20, 20 - split: 5, 5, 5, 4 - orig: 20, 20 + xy: 11, 1 + size: 5, 3 + split: 1, 1, 1, 1 + orig: 5, 3 offset: 0, 0 index: -1 -default-scroll +default-pane-noborder rotate: false - xy: 78, 29 - size: 20, 20 - split: 2, 2, 2, 2 - orig: 20, 20 + xy: 129, 33 + size: 1, 1 + split: 0, 0, 0, 0 + orig: 1, 1 offset: 0, 0 index: -1 -default-slider-knob +default-rect rotate: false - xy: 1, 1 - size: 9, 18 - orig: 9, 18 + xy: 38, 25 + size: 3, 3 + split: 1, 1, 1, 1 + orig: 3, 3 offset: 0, 0 index: -1 -default-round-down +default-rect-down rotate: false - xy: 99, 29 - size: 12, 20 - split: 5, 5, 5, 4 - orig: 12, 20 + xy: 170, 46 + size: 3, 3 + split: 1, 1, 1, 1 + orig: 3, 3 offset: 0, 0 index: -1 default-round @@ -66,40 +88,44 @@ default-round orig: 12, 20 offset: 0, 0 index: -1 -check-off +default-round-down rotate: false - xy: 11, 5 - size: 14, 14 - orig: 14, 14 + xy: 99, 29 + size: 12, 20 + split: 5, 5, 5, 4 + orig: 12, 20 offset: 0, 0 index: -1 -textfield +default-round-large rotate: false - xy: 11, 5 - size: 14, 14 - split: 3, 3, 3, 3 - orig: 14, 14 + xy: 57, 29 + size: 20, 20 + split: 5, 5, 5, 4 + orig: 20, 20 offset: 0, 0 index: -1 -check-on +default-scroll rotate: false - xy: 125, 35 - size: 14, 14 - orig: 14, 14 + xy: 78, 29 + size: 20, 20 + split: 2, 2, 2, 2 + orig: 20, 20 offset: 0, 0 index: -1 -tree-minus +default-select rotate: false - xy: 140, 35 - size: 14, 14 - orig: 14, 14 + xy: 29, 29 + size: 27, 20 + split: 4, 14, 4, 4 + orig: 27, 20 offset: 0, 0 index: -1 -tree-plus +default-select-selection rotate: false - xy: 155, 35 - size: 14, 14 - orig: 14, 14 + xy: 26, 16 + size: 3, 3 + split: 1, 1, 1, 1 + orig: 3, 3 offset: 0, 0 index: -1 default-slider @@ -110,20 +136,11 @@ default-slider orig: 8, 8 offset: 0, 0 index: -1 -default-pane - rotate: false - xy: 11, 1 - size: 5, 3 - split: 1, 1, 1, 1 - orig: 5, 3 - offset: 0, 0 - index: -1 -default-rect-pad +default-slider-knob rotate: false - xy: 11, 1 - size: 5, 3 - split: 1, 1, 1, 1 - orig: 5, 3 + xy: 1, 1 + size: 9, 18 + orig: 9, 18 offset: 0, 0 index: -1 default-splitpane @@ -134,14 +151,6 @@ default-splitpane orig: 5, 3 offset: 0, 0 index: -1 -cursor - rotate: false - xy: 23, 1 - size: 3, 3 - split: 1, 1, 1, 1 - orig: 3, 3 - offset: 0, 0 - index: -1 default-splitpane-vertical rotate: false xy: 125, 29 @@ -150,43 +159,33 @@ default-splitpane-vertical orig: 3, 5 offset: 0, 0 index: -1 -default-rect-down - rotate: false - xy: 170, 46 - size: 3, 3 - split: 1, 1, 1, 1 - orig: 3, 3 - offset: 0, 0 - index: -1 -default-rect +default-window rotate: false - xy: 38, 25 - size: 3, 3 - split: 1, 1, 1, 1 - orig: 3, 3 + xy: 1, 20 + size: 27, 29 + split: 4, 3, 20, 3 + orig: 27, 29 offset: 0, 0 index: -1 -default-select-selection +selection rotate: false - xy: 26, 16 - size: 3, 3 - split: 1, 1, 1, 1 - orig: 3, 3 + xy: 170, 44 + size: 1, 1 + orig: 1, 1 offset: 0, 0 index: -1 -default-pane-noborder +tree-minus rotate: false - xy: 129, 33 - size: 1, 1 - split: 0, 0, 0, 0 - orig: 1, 1 + xy: 140, 35 + size: 14, 14 + orig: 14, 14 offset: 0, 0 index: -1 -selection +tree-plus rotate: false - xy: 170, 44 - size: 1, 1 - orig: 1, 1 + xy: 155, 35 + size: 14, 14 + orig: 14, 14 offset: 0, 0 index: -1 white diff --git a/maps-editor-android/assets/data/uiskin.json b/maps-editor-android/assets/data/uiskin.json index 551e3f5..2dd0a5a 100644 --- a/maps-editor-android/assets/data/uiskin.json +++ b/maps-editor-android/assets/data/uiskin.json @@ -39,7 +39,8 @@ com.badlogic.gdx.scenes.scene2d.ui.Slider$SliderStyle: { default-horizontal: { background: default-slider, knob: default-slider-knob } }, com.badlogic.gdx.scenes.scene2d.ui.Label$LabelStyle: { - default: { font: default-font, fontColor: white } + default: { font: default-font, fontColor: white }, + selected: { background: selection, font: default-font, fontColor: white } }, com.badlogic.gdx.scenes.scene2d.ui.TextField$TextFieldStyle: { default: { selection: selection, background: textfield, font: default-font, fontColor: white, cursor: cursor } diff --git a/maps-editor-android/assets/data/uiskin.png b/maps-editor-android/assets/data/uiskin.png index f51c5bd1f298dd4f4779797b487e848065f8b501..336fff894930ffc23973475f0de8dcb97c932929 100644 GIT binary patch literal 22786 zcmYg%by!qi*zFm*Te?BITe=0LyE~<&d+6@&1_eYBkS=Kf>FzFRkcNAH_q*To+<%67 zW;lCxzVCY1T02@*`8_%cF$w?x=<;&X>Hq)%-vUr11n_mIu?hS_cUWFpLeqQc#0WVr zLF+MaERK^9&4MT{K*@7496R}~IyFW59c{AZ2!4ts#qt7?-_JB0bZpTNdcRWw)^BT) zcXMAoL{8}ww}r$1<&2X{m`wlW7i*f_w z8`y>z0MK3OSHA%ak-J%)?t{<(C$Mcp;5?04hwZ0ov1Hx-5K=*9d$Jwen_RhPK@!52km92~*Bp@lpj!KDBvaSC76I_z$5?>wHsJq- z4QTFXeWe6G8+Z8ry&4yLwb^}Do`nbg>j@mt0O>1l1vc=To)v@s{j4}38QUI zBjCRueJ>%74nxf?_V~B{XQN~|3g-X(z^eq-L^vRJyW&>&&TWt9mw&()%amz@hfDs_-FsJMaO?U#|AXNDNxt?UwZ`6p6Np5ip*8N>RaS8hOP)5_q<)JymY> zzrVy3?ftJ~{jC4ZU%`a>56j_&r~9kHz2E7~63m6ily<^No}=KmBkQU|cbyJb@hZD+ z$Z$+o)ofA(aNwZ3>~`7p8*Ck(A8z6sjFFA*4T%v^3D`*O2cOuGz;=VSkH(u{278{Y ztpb5qybTyAxO`=JTy(dmV#>hA)&CuEfhzD>qwxcy$G>6w-R2yu76~Hp*YS}mAHx1N*`ET;ffsK!k0ubo< zd^61I7Jid3GN4*28$XBw_;;cJK~fvZk!u(cj6x??-?fcL{756%|6x~;pIlERM4r~! zx0QId^ut5D$bpxLZW>IYk`I||b>QPM`MmZSD(Ul=1(E;w78W?S^gSrMy|ii#-~B{9 zg8A|^dgicHe3EOl3=U|-1OphwwB;)2J&)Q)x>NW0s^_u(MGIN@GRa3afs{zdVj#|2 zYi-1amiFpcT-?gPF-P1qU(7i`W7#R6AoF3>8~})}U10zjHC$k{o2IQ-2B#|LY`aa+ z>y%U|;Nkjxm4AdxnX(J8QJ?K*77Pn~za*|F>-;yiJYan&9`Eh$W?^7L|(G=1=h|yl1=hJq>mGi>naZGUCp?W$K03?L?CWDgG=PNk=p3&4R;k~{Lpe5({z$rIrG z7fBH)_Z_!nb-KJz9DHmW03U0ds#20{(2i;HoHu|#rSq_E=h)JPx|}D9B>v8u1d%;H->wgKUi}dYsvSuDB-yaqVeP7t`9cEO_*S*| zxaHfY>tFQ-7>FZErTnkw%|BOtyFcXeIilTkFBL?fle`Q+J%JyMl;&YMdVSMdnJ#~o z1)`3VO)kD@gMT~XN%;9{7F&-_0%)H;fBp>jMTHvZYdqP*cE-lWj&OD9k9kw)w*TkZ zZH3|Tn_&x==iT`xCfUV5272k`ReStf_ror1Kn^XjO&>xjdOMw|WZtM9xDU4GPmG0I z@;{@hj%@!^D7J3u{lY7jNXRMaH&lHSX6q~?LiM0_>M>%001czIuKWlOy%X)lQ_{Q7 z-l}T4O+J^OKvpZ12BPejm1G;Vi8a0!ylJyo`?|6D^`M)k^R{)D6Bat19fYj)6~J1z znP9bo5g#uQ3Gk&rF5GgEoT0}Cww64F)bc3B_kym*_{sY3?h5|DNtn>#{X>9&Qo}f21g{8Y@CsJyryPqSwho3dFFp$PpL9kM^Q$qJBJ>q2wex@NJu~|(adBXe^zr-Nql*u{ebl7^bhqhm z>+R9$)l|f}z|Um|6Da0<&I$TYq9zyi+x?7tl+4%ZqrH&veLt4Mks?vtNYU)uG_?W# z?#K%oBVMzht@F-?xxmFyv!ujvUkZ<;L^n0fHG_e}doKRY>#iT?n%M#q2JkKH3mAZU zQO0VA-z~>e(CD5Xqgw1*i`)KtL~NGtfzJ;uPV1e|ylgF{eO{#EtjFaY4v5Gc|GHkE z&v?ephE?mgo6wtg=w3SadkJZ+b5GYgM)+0-M8;UVT*WW9+ET4)E?T}lWgzk1Kq0W? zNJhP9S}eEpHh$-S`|s?UJfWW&*Q}AiY@=-`7m{oXjdxdZxXJY{Azeh zhHz&$gSnuNHbgphMxoGkkiFgL6*WCsGOv3oF(uvMlfw<4Q%m7SLbxJY8wYzgu};sI%yG9al~*)+m!x z%Y}waX%8rTcyBjdfj-zAXk$(jIRzuRiw7Mg!NZ%`JD4mLo3GNchrCc2b^6~IH=nYw znN$i3ksP%V9)I8wtBN8@J&`e zPv9adDJiLwaG$U^|1T4e#JDyf)kVd$j3$XN|TwGOP4l@rPirdDyw8&x;L7A zCGv6>vjl16Kk6%!Pfh5nSFuOv)-RCBXNqeUe=qI;i+CN+zm`QWt~R^rQ~de$;jpqW z5dTYg8D*{BRH^p+-55pjXm}`k>Uv7i90n%l{t`D`9xDQe zGU$2UJ2y|NP4LyaL=Ho6Z#?S$0dLzqG*0E0_%0m3L;zyC;=0X7cd!xslL8a7irI|H z#oAhf!g<>{WW?Zoo?^tjk#%4fP zrir<^IR=GTU>bJ|R8?U>7dCs=XbDBGO2}W~xT^BLY!R{oO9P zJfBCjF0?p$)n&8|L~yAeUe~p(`v&xa-eEuchGjP#UV2Zytjgl5B|+d zh6H7H@PJIyo#WKV-^%mw!XOX_>weCM_!)_%^^# zP3kV4hSz$A2d@z!TL~F*9B+y(zp<@cyZM z3avamaHQD~>u7Ms@#_CFc+e{&rzb$iH3b6@j~h5b9bmG>wrWcx;dhE3HDN>V?m%|QiMb_}G~qj4@9MJ4 zWHZ`Rw}NvU_sS|XPx}Foqu$wmCOhU-?y^u;o~zNTFX;_POL{0}rN4L@KW`Bo_@#=- ztX*?=c{p7;9~{UR+%i&v-f`~X7Z3NI@p2uj1Nqzwgno}SGtoCism^eBDT_dB;|{a# z_YLpdKpX}Hv*Z#%H!WCEbk5g-ELj)C~i9nG_`)6BRHh-1!%F^JN zLvWy24`fxSLSTlHz32*f{CCfD#-=i9r2SByarR{|zV}wg9RMos6XSGzVnxcHL*P;Q zqN4J(d{l5C>6;)vpZ+Yhz1DFs9{q+{+dq>|7>QU+BZo&vlft0>|L-Rhtp^vvo+%}a07r4v*iHfb3 zXs^wFl%G~dFG#4nKJeUD_X};ukKcK%9e>+hbs+y^KZgRMs*G>lYs;tIoFPv$(I2+7 z7*&R9&96w>2aPjl68L#=LO!#PNrTHrn08?IlX49A2aCxEPXGFUL zO^3I8d8$lzSSRl=^E1s4Y|Q6BpSDmKmRpo?0E^;wBh!*A>B(aD1fUA>5H!9cA@5uokW&2y0>6=P zTOnZ>04`dYH!mvUc5*3zEXYJdx{^A}G)B&Ayin}*!|mxR4LL55#XJUgeOx9h$Yc|< z;Qh}f_fh^cnRt<(xFJtPBaHV~*+S2#K2$j-slN!8d7LZn^ znT6~BkcOVxlUR`VuacKhg%Iso@j>mVMcBQSEl4u)Pxxksr75pbRjC-LSV4Ya=9Y{7 zT25x;w)gWoV}K%i^=+kT-)6ndxUrC3+#f#R%Du1 zU+YKy;H-M76;~|^2!NIgqZ%Nf69(eb*DNlp-#IZ(N{R3gGZ^;`A5)eGl|)flfI*=n zH}r!={y;rv^aF*ecgol^UXV~I(Of9u;U_;#G=Zx%j+J6xEqIHjU;z4Wa^RdP=}D)i z%*~N-j3ce$LuP{FvkBImd@`DQc(tNm6^A)L$Mb;uEZN)7$3T(udS{y@+dHSO>;&th zf^d{KOiufI=;-KV$g73E2j~s4+aB6a4=kxX)j<-YJq#~MF0@y;Lkw@@;kz};o?+bw z?>l0(8j@jvXn6O7$3T^jDsdOsJX8^$jKUVo1K0L?eGeN~%A?b5d}vC%%_Le17ch#I zg2m19U*mKMC^>N{uUeqVh4H@ARX$gHsT{8~JWgkrM{{2vJ!T>JR zzj^HE9N50WQemXt(rW>gA?|@oV2sxmmL-8Q?j_QsmabOw9!^`bS9@M|F9VsWd_MJTwBg2?EU)!6kvPW=1<-` zWedvetnELyF%6y?G-H~uwi9<=1a9@>1x7L}3Am6DLl<*MRCSn{J15~`AHIgi?=;4G zhJcN1xL7CmF-~#0z3(JF>e189x+Om~S_(cs<*4$&2Hkby&N1ma*xw-k=QX;`q@tDy1LK&C|a>>Dx z=b;j4mJ`Ky2Fnu8dv~JiRa(qa?tKbs)3J3i7Fvc|==!nO*9o6J^nq?+;(^Yns3^Wq zjvv&o8lG=bW*?^OcVoV@)m2)NG=$KOK33zpq>cl}1dLXhmJ#};{jv7E!~5DXa!1RbyA zlKiydrd4s#?@RgcU~E}3x{H5cSeRs;jz)&KpK|gU1zLdd@v#GLYSz-73B9spEzo*D ze53v#9PF%X(q2b1c6D?gM_cuih!8^VY**S^z{(3l_s!?^sVO6+ zf!lI0=D8~+Q_HXty%!rRLzEC^RYsr+4gs2?2?h+P>dUd4tUrHdv-k^iyWcu4eBUH$ zq3@tm{-r;pnVe>Q5Q#vh=BQ8rCQ7bUIwfC}ya*ft|)P{jxkex0jUW_oG>@Hx0mo@5ZFXV#Z`$26j|1qHI8kvz|(6(=3dY9lJ0g7e*6534qD}xbXVWTjxU;9Q5>sL|9cI+Z&*G;7r z_Y6%|phm6k_iH+UZoTywK2hYxAOO%Nc}YMC4%hMN)RBwPrPmls&XgOXVK)}s%ZyRJ zSeG2ky7zqD)h!BSCQlUF^1Gep`0z1mZm&YvU8VdG*L-Gi#aA}fA}E`paEBc2j6tO&Z+04pN)CH^dSu1%9O=z*@5J4&#YqC% za}IvJf%}{iV?v-b09CZuk1O3vGGbuJzG?q3H80q^Ik}0V@ox(+s%!8E< zro)z)Vy~^rwY_AaXh4pAcb|ih&Vwru!l~LMig#}}7k1!^X4cB>Wk z@1sxVTjpD(M!5q@j?>QdMA_fbX1IFqY3Isy!<$2+i^)IVtv=I2_nGyc+iU^0FTgfo zR4l5D$Zsbat_*C+(^Ojdls~I&UKDVy9&BHmlh8W#T0U8&N#h_aEXc}zJ_|V*kn$*! z_?k>0)}m27+eV;$AnLf(z*yT%lxr?Sz5?dtfjidh#PMx~+l+}mQx=Ru@pPhy3o_<^ zkpw&&cK>3hl(p1&1!I>~bsln+%1W8cpU@hJmY>XP&>JV1#Wc)0-hTZ-ZFaPjBBgFL zca^6FM~$Km{N!bbeQrQ5sx6URq*asUh@9k=-{M!~7Fq0h9y#BM5*zMKN^?1b`M=ck z+Em@{`bDgQF2LC6@XCz~5pn?s(>y|f@5Ro~1} zS}4ULu1@urW$Ne7rvU2W;2kcTK)!-fTJ`6&eY(_r9}P3bNx^S~v+BNw--Z#3$sai^ za1GUmJ(L^rqz`!O4M`3{1vB+%XDokO9K!-Grf#7S&mjeGE0FiU3CO#)q^?l)C;H)e zvc!f3(F%L()}9NdIik2TOb|3UVNW+aq(BvLsUoC}qiU0ygIc4LnZmb1cdPJZK6KQ; zm}U6MKk*EWVO@MHo2uoAZO~XUgGp5=T#8a-?nB)S8*0YGEn}x**3F9Vv^#PBnEgbV zc#Eaw%om&{dHsUI{T8=G31`rTF2%DP`xjce&As2s({`WgW#jc3BKkA z4OICJm{xmicr417e7Lq-JDLlw@qI!0$@h|t+2!2r4os=CC}e#eoJB|3_y10oQ{So- zrR&+z|Bka!NxeTVSsC%N={<|h!+6&?d}foMs0s2u_OxzwOPl&aFI@X87^=9L3~JOQ z4+5(ZP2@0uXFHIUDtboZaXhz67hwhp1gD9S9Cn*_VVw1!D~Y#wIQnmN>t; zTu$ur3N@bdj-eH|m8|X=;~Egw7k&rD?pyJdBL&&zo~!v666!?b(kEPkOmbh6rr93~-ffBR z$U?4LaL5O2uwgrE$nvRl`BBz!zpkvez4Bc%d6HqfNaUHb_mw}1BtRI@{;St{8k*X7 zeTfa%n?YC_#qmjfQGA~Dy{0y_igX*A0LAdjO$<-GRig> zMPCV_*FPrw?VSPf*dSCV`~Dce1vmD25_+d!V2``f(zz?An!kfNy;H?{0Ofbu*0ssB zhU#qTE&fFME##`3vjs-fEBQAx+(sC)vgS9_c9~Q|DQQ)Fy4dPJ zbknAJ5hho1&yDg@{!)90U_S+BuW!75KlBdt2*2$RJ-K~St#-j|4r8bnixbCNQ``@?Md~wx_h52?-<-*q2~|79oOwi@ugvVce6`dmQy*2T}=Z!EB~f3YQ(vG;_&4~5ABLp$aJ?D?=7Bb zh~)fJ5+5F@hX{#l2|f}Gp0uC{?P+2C5%l`f2>iCU88296B}w_K9VCnT6K?tzpWO8A zvvM-lUL#ZU_aMUhQV=qW>il$Aj875U6anWyzeFr4HX8Sj+1qi3unW$s>Kqsmel+zcZl_}C*>qi0@MKB-3hHc$9-(=WNp-7+` zfKYBc11=FWTCBLZSP$y^;p-!RR#1Y^hC5(t-O^JwdQCOk*fe9A?$8|(e%i-J$WJpp z0?KU#>7ChZ{a98qTl-HdKJ*4IU1^+~A~QimF8!v(c8Wq3HFSBUFnn{qj4|oxTK_`z}pZcrd?6>cl+_32Y__Z4CPeYDK(6@g%sVT0|Yxd}c@c(BRqNz*z8r(MMU zCQ=_I5lx7J@p!M30rv!5Y1eD>SJ=|Lnl#AeOggATnXwswt5G>f#@AK)PDuMkKT#P#&`#F~tojSc|KKS9M#9H#rM)9*r0 z)b~*Cz~Wv9`EBWTX;2^<;46ll$XMyJ`hJvV{3(-<>YMaU!Dx~sS#k-FousR zSJeL0zb)dekk4$**vV?wrt#>a;txRG@p|rjWwo}9<^>S5s^hJH8m`o1ud_-!TDBwx z93p91Q@@Qs{IM#S*=~wb;NyaLbc&Q1VC#`Icq^xnFxE98rZ6rh7^j5-*pZ#qb(>2^ zf5hR&eFs1p;o)@m@~(7>#e-qeexI4IJTT0sCK9Uj=7)V9)Aa8IpxFEIfM&Cf?$_QtDKk%7WqzOtGsa+6wV!>cCb9$xt+Vk4c&AMfkofI#_{ z@ZoJ|s2?D`TO_KfxSc~Q=te)s*s$9irHB_)@*hE6PX+)T@z*oDYSuodm8}H`dwT2! zg0Ns+!*Iw^<0a_+zM$Vl`C)u^8$KSxxeOYzWcgQTq0P(Lhh%^RRwS_S$5rySNR)v! z=X)X6s&KKrBSx&Xu8S9ZVN=*9k!rx1=wSP*9CO6q9Wsar{D{tZpPXAfPf(n|X*RfX zetupkFb|w#S}985h0@Hnx|+gN1PbkxUVJ+O<%D;YUc7$i+Zu6(ao!opuHGPgI&px8 zqG}~u3?_UXxXVLFM&9fUQ3(Km#_#Jlfbsj5HZGL{VISU&bi;$aw}a@8#5AVSV5U$S z2z~9AHX;;o*%)uWX!zTtjR{vwC>ZB@?Joh+p|*-<3ui;uhtomSU%t%Z27Zb;6_|Eo zf692=ADpSyac*}oX)ojC`80)6QmQD}g0wZOf1t9W=81ZeS8PUzA?HlYRZQ+rg{GWb z>n1MVtu6!{tarV7%6C2esL8|QAN-RqiG z&N3|EfSX>~5Wx_4Jw;gyivmpKa*(@uWfzc*c<%{&pKplwe-x3TD*2f8p8H}aQxwMr z+>=Mt{Quf9#P@sXn?U(n$^1kZly5v%n3$i3YNEFH^pId9F~~kz=dtaS2jtDq*$xto zj*OE(uKK$&TK*)U%2q4s$ODsS2*jry&yIHERlZlKe6Lp^acG_a8W`o- zx(UNsnKdw{`HGqmDd2$`UpgI8i!MkLT4)r$e4##biIWa@58_^B!5?4c0KyFy^%J9pX>{xZ< zhdeU#e}k5wT}36~^5E9KEbPD}F*41RZEiG+vy}RZ;nREChZfXZryDBkzJ+u62SZBF zh+*pR$G30*VMK*t%O|4hU!dIFH=Dhz(Q*+PEgCUmdaXtam5aHeOoFA(p~d%FKH#?b z^He3DVt2ZRg1@84h>#FyJ*E0~?4pnfN@!4!k)EB(uPuyQ$T+VdZLM%z?LFr`;zyGe z^BV)$$~sz-Q&b0&9lPUHs!m80zd-^20%UADrZ~NpZi&n~CWiY%okCYGl~lyHvMxcR z%x$h5eA^jx6uCs>R`)}?Y%w<}+@8t;;K+QeGe`_vIQOBOEG(egm8He|V#}=S>2d@i z=5q%;+wJdI zlmXLb)2Z?g?=P;{=OtERg^6(s&2Gz|GoXly zfL;5&l8Q;jl!z%fF# z8a`J5GT`jhe|)J1Vy3R7tG{!QAskesN-VEAX>=fKRG2iZP5R&}6{LA8KEyk|Ty_V; z(yJ7Tc4+*CeBdPD9}+0=Yh$Zls5#OM60Oy5JR&tKY2|a|Rvzo&rrIzz`CGSMVL!o3 zfYsoYKpZ~$y%oqWbYTSc1g6U|+q#+X1smmCr zB@Ahy47+Tiu#m1d;AWUIJ)ZlTyR(y(KzzaUE)mNz>uv3LY=TZksQj5J_!-w)#$xN*h})5;&2Od^WP2&--EwY5`C}d)~HZ4%i)xhNJPf{%q1H<%H_1u z{7>wg4oNHdIHud_?-K{` zrpEJGF)&I`RtTF#n0F!oqlSso7lv*Q%~&%4U$b9f$R=9_7RG&sxT=T$wtRfn<=TGn{H&DKok%EhjO@R$rH@bV`S{F<4>cwa z$S6TH4B|=g8}6{xGP}}aGHRj$lYL7hJ=w@}G_Rr|pP=A_AN{NZ@n;c_mFw049g)-=Q6d*`>aH?iaaIG8XhTCN1j8xm-sx{#V(&k9!77M8nXMS2ok zLjtLjrCFE>76>=?2m>vt2Uo2N2(6l94f+=MqJPl$qN$cc%k%9VHR*_d94Krb31+t( zdD99G7LqcPZ*-o#56&yExKGlKV)+jO$E<)`h9Ax2oX%9`84Ti$WY1R395gm6fJp8(c3p z1E0*=GQ4fx!N6?q@hn-#xS2m(o)Er&(Z6wM=X6n3QGl%b`6{`=wan-m=1?5V&`v$8 zUuzli_6!4+rX;SQOZ{JGGn@fZZ^eyp-Veso{lLI_&rjpamqwn60UygWx2)~xT!C*W z7b48eUf*E|?{}JFB*Qw(T9s+f80p5HX-@p4(2@^@9~}7Q`=Jjxa!mQ*NJr74xie9%8hmG3ZX!7+R)*n3L|M9!I2-A2-yFVZ z$@Dem)^n)2eL@l6$#7LVrs2*W78*fKy0<$h4vW*!XDs}P3qXX z?^t;sdquh1#`UAHGcNILZ*33L<1FQQ#D9(%R3Nol*tOj!R`}-Oy%$Hw%Is8{$Lgn+ z!bEa!!~3^E|LTP82>CUT`%-q(xT@@kMeIUMQTg(awk}bNsa}hDaq-ZA;f8(6QL*+( zIZUXrmoW&cBow+WaP~0)($*O!Fe(u>evgC)aPYlb=?rL#qzf6ZvhSeoA*yFJR~QC4 zm+QuhZF`y2tgI}rK0rW8O$tT3M)yL5Gp6w}`L=~vYsbgU`!yT%W}t}#y*&AW(hka0 zP0<&h));~yhc~e=qt7QF`r@tglXl*H+VNI^?xDsXWT2lEGEK&tX=fJxh21xPNFZ*5 zxkBO)_DwDs-0!=o5bMgaV#&_jK)14IKyPUA=Bf#^tnN!H(fpzwPTOo>W}ZI&^mx{l zc(YfpU0`E5YNf8nYXb0V=q=fo2RDEek@tryTVmAr%#;p>T_Cf-zmwCeQ0VQ5A9pLP zM2?bhsFBffQjR-l(D?DD#*TFMZaZEWAEe8UcdWLPC;#+DV!nP@3>`m{0?Fo;U<(-E zs;`_2R&5~47x?f8z96hYvgJ<}1vyW3HtaV4@bbp;9)kzF_*P%E56&@U#@PWY3iqBU z(58F9zq&fw^~Buq+7nMJ>0sH2XaS%b`{rKa`=4nvwyX0i=BP?VpdbkK2Z}hHp$Gjf zyd_^D9H1TS&~|lX6?80yk1E9VSO}b+fgbcJ?Y>gqpm9AmUKsedotn^MZCmW2!ubnYn}FTg^0_tXylx2*#>!t3nO8n z!ET!RU1e>|Y+DC}Z}7_|1N;{eB-6pCyYeGN?dDTZ^VpG8UiJdc@U`#3uk$E^JIX zd_Bs2)a&*h^ze)BbT8O=L08Og!7=O;o~l5W?e6+F{C1lCQz>sABa|=@e|G$fdBbdt zWVB>u%C)ozI$S3+MD7L(UO)w)F&Vd@@}psnUEkNII-gNeTl*UG8b0(JbO^`#N1*%e z1O#U5K6a(}KnlpKhP0)zG1>a-w#ayy$2scO$XKFiBeWwWcEY&wf^FG1ryfi88E^-r z|G|={^I`kX0~NIsj!H1VV%*;R7reQ^<#Vm3N2~o1JQlFBvMpL=J(lBc^i0qv^2_`j z?alGUHRvw;o09%coUr{J-5tMwm)5p+iS#dq-waM{)JmMPAp>fzTB~G&?zSJ4I1r;f zTr@RjZY96J2{)_0{=4W_dhHxj3P_W<04IP4EfP*^y=SEV8+UFhdBW_>GowzmA zbxb)C$LkL-OiYc&bJyv9ogeaQEg~iURbejdbWSQZ-qB6&YqVz`I0ocL{PxQ+9i1P! z50C8=Q!dK0hAHw5n61)kim~@Btw~yB1+FBDWZhS<17HM!iKm0nHKEJMpHNb3d>$@ zu+vcS&tlaAhH`k1{e9*X-{Fj~NFP+7Tt!~E>kZ)CVQJeDvvB*Kp6#+pMgz~C7~Stu zK#v6szJ($5+D8+-@wSh`J^9HvjTwKP8$39B#1>hF-uOdE7T%=zr_0fSjm6(6!pjo7;Vq1t#{{m@YpTu|n2-LW>W$t` z#s-wJuk4G?rm*Y=AGZV*tXtIO(5Hldp}SanSy8TmKozVhycND{Gd}wff&A z3%8E$TP(8xN%E>e@1kzJPW$n_G?toSfTT3GSQyGr1#-S*tuI$G49e83+9394BmTt! zPznqC*AZ<40WYXxVqMt6$BHl^HSbo`qu6~-AFdD4cfk8|hopM_>i0#Wgj*ci`~#oX z$pl=?>szloMavb#Z6E5XXI1FGs0wTCvO%qG=Vs<8@#; z=-E=I<<8Hj-$(wFPuM>cM#rdL7KK9z;z>Ns!o^=jQg4#1u*Tuy2K7fO@H^|d9_(nN z7g{~+rP#N(9W_$Z*(x|_=YG`3Z?EG`jS5l?Wu6fBhB)+Q10Ro?PhHUo73r$AOw?P|+ zm&iYeoc^JX+n5Ce(73Jm#+MVx%jK2pPUmU}0S=y>3q07Xw_RNM->P3f<$#$gt3qDO z5)H=P#==Sf+-3OSQNuxS84bB>SB&;opxUxAiBcY@f~BSbNjD?JnNn7JEMv~d5sP`Al-H#VPFI*bQH+S zTMU2Gh=drIX0?TEAbgL$gaKp+Y~;{QM&`__AaPGD(DcOx!8I@eU(lh8FaaPpggmse z7*1Wd{3xC%3T~N5<0M^Twy~ISQ<-nZ9skU!gsz$Zpl>PNx>CO8pnIM?;JQc$=$2-| zp*yy%8wfIWTGT=#?Ihp*THDaltxJ2_2C^_8(gyEGMW^vO5S~|9Q)GoGU;~*hW-Q|7 z9izUZTJZ*s$TJn2Vfr%4Jhp+GFuTj|_Oy_w_pGJKV5^` z5)%umvurV*005kdxS6yqkBl!$iqGXvw4m!&pE5nL@ZAhH@ppot+aB62FR3zk1q1cY z34P#SSXbBUGTkaTHR4q()J%x{He;}4&Pg*5Ex1SkMl{OAh&tn&uZDHz{heL#07o{7 z49gmO+kv^Gy1x%RFpU(ozIJN{vjqz-Qh!>1XDv*!_V(Bw%OSLj?{lNr*ttETUe$1s`{YKT*WQ%3j2>W=clV|Hu550 zGEie|dpq1tINkf{`%%&N?;OMzFWqgCf^zgc4zf;54R!=k$q~IISkCJiS_MCIs7BXD z<2oEr3EOf)sZwMY8R*bDyf3J^Mb|@ec;>bZ?pzE3O8s`gK-%gU*3~Zr4$*OTSCdBz zx;>b9i@U{_IILV|!Ga4|zWA^@a&Svr8(5yI2tlBqBl9A$M@PW>Z}DqJ;8iWy>Ye+~ zfT=<+V6aq6$j!NE-thN{mKY*pEia+;R$Uxb0$6^9r6>BCk7eI2>f63o*D zlmMlfmD6NNzfDQS--?``j@dc5?3^LqL2pD7x<8od;RbF9!Hi{A}ew;%cEvP>F`_5pQVCY9{!zySFid{UVpFMFp;k-_;&zl zPDn|qp9@hK12#ely0`muQGmM9BWq20R)Qj67=|cv}DYrvpaNjUybh)(TR|;71W^AWZzPy<`)GihjyJ#3&3g23lIn z3g}I$u~kdbsvYF$JN z^=c*`h$ZfsHUAz}a!A%ELBm|PZ@dOA!LJk0T^cfkFZ`YNF5S{xv$Q}+T~oy5YBe+I zrSrr`fJLi3R#cLtqAW%8fkUn{=r_)Sd4g3C#onlI0Ke4>E;3olBvOS`IqOG?R=qW; zM@bqYu_;>&RP>vs!)p1UGLix7I9y*MDo*Ofj%()pcDEwA;6vVLW|~tcIvX;!CxR+Y ztS(jyqhEVXFT~^*$bcd(%FW6;Q;zO71~ZMhl>#d4Udl$U;bqV#NHEfPZ)LT>iN?>z zu@wTNwX5?No3qCP3QLyC=UH~4KCFZP6Py-f>H29WcBd0YE2>zt{bfb2EWd|1JR_rw zUj@aA?kD|yPUa%S7h|Lti7I-0-($~`LHg@0t|F~mS4J@|nukQoB_b$$Yb4>QJ=2WZ264K(aG84vRzbjzwNe3o9lU0 zb7jr}XQj6mGe{vduHh_65l23Q_|c=-GU3m^?SP z(x##g`(|tbz}u0>tCdyMmusE2d@`0jMmdc}6^Wj*dHxAGddyo&e9p7aKAV!!o}4%3h-xGL zebS^!m2K=W)2!DuY0`v;yq>X~4-k|(b?R&ktm3v=Wt@z@9zA;W73ILx6zr3{VzY%- zGUJ*x%3KrIDUwP$+%tqhpTGY4>%YvLl(??9(!SJmC5yh^ zP1#C)&2kQ+`P}r}q{KZgnPB}JSeTE|dP(*y|^y$+^)RMVPmMNLJ z&tKW@>g%-+Z22^UJ-T-7`uV0!o92Ug_DJJ(K{536_A7+@0VLCg0P!K$rWw{Ef$fymY46->lx&EY48$lbDK%l0%FuUN6- z2kNFtk|gQ6ckkXF=g*&4Q$MGQh=|~^XV-4rxN(%Z`tV)9l`B_9NuK*5Hq4xD^XAP4 zadz+CohSDUu3EL~$C9{ntxlzwKYu>Q^zONH=k^5$2TuY6_1?E{U#DHWc77&{r!0=GBZI>7AAUT!-fqD83t`h z9!hcfbsp0V+M1;3{k7}Wt7mQ{Zey_{Rh6yBm-_VSlSWZ_UI6fXEqyrFwr$(Vb?erB z^P6wJ$*qX|<;~;e8OI>$&xe;QS1wYH%iF3vw-%Gnig#gGo;-PI9~x@e@_oHuzkZ+l z`T30u4-X%pd4?kZa3Tg`6*?u>MRTj{`=XavjTXLMtK0HPBL{;qrtoZf$xwSqBSuj& zm}A+7`k@%;rX^0CI9fV9)4|gJ92jUjcQ&3nb&3|*qh8dGLSn2MEPA0?*q2sj^qeIE zk(2omPj=NDeqx-O+@L{&c@nio;%hCQkur*YNgGQE5QhxbHQ;;lKGI}|f7qErsjk*(X8It^#=YDQT z8XTvCkvC~T?}7ebx^(GHFe#6{J`b?}E=7tI?Zs}Sv-i{#efk{`Zxo38A{b|Xv0}xt zTfoaq`pNSqc;3R%$bnGHQWZx-c$98@I&@>juWy_X*A;#d+iqV+7@mj4~ zwT#gA-Me>xC-lKNoWtDzij|+w?FgD?+(IlC(ZMn&V*&#MzZWTDhdhIl#^;)6I067& z2E%aE5xFV5)T}aaR)d$X*Xp)><`P2S>iszeV7?G*2aW4yx@8+`4q?o80RaJv#eX}) zrdveycQ84Zu|gcf>N?G`x8AOVieaUKVnniYKRWF`Bf8fIG0y_AQmp#}ZJn@j*nvZ_KropIfH z?r4wkZqb|QJLjfU)22-q z$U&{Gm7pc^!AD?xx;#GoZ z8SniE{?@j!co%^vY=nAt=+J?2=uu=6+_TV2;7$_H!wEy<0|0>nB#}WuK~o?OE)=oZ zpfSZD#c}8by__q|X$v762|djBD$LA2Rs=zRE5C8Y+y&y?2e4#x+Xe2D!{_pfgfo~s zJ!!k~cu#a{=-rIzkd^W@lW_33Zrp!K(`OQQt}0cke8|`b)C=3TZDYFgiUO|QEYfM2 z29C^m;>3wBJ^Edl=|v)$cl`MAQ?glXqT*#y{o>v7<;yoyHUhO`E(N{=;5iOz0}pRG z&-um5&*Ug4G}eF4^QREE=gQ7i0#gI5*xZ2id*Q-`bLf2WW;m{c=i~w20C?B8(8I?z z0Pv@e-*Z1&lAfJe(zX#^p5Yw;%=6gMxlg3_6@yz*6#xz&K77>1c=MJI!5&G(mpzj~ zMYv~!1^~2YsquQ~*F9|9SzI;hq8~eWbTaqtlM$gOEfK?GTrJp+06<+@!wiW8iJ4BF zNlBtWa6>@Y^JF7XlyxPi9)}f{qTN-CsBfH02=!s3=zLWa{k#TGI%Z%G7>#<~F!Axv zkiXRx;-gDx1c>Am#&4+){pQ?N#TZ_uT>>llr{bIb9?ZAj2K1g05jGm*H4XRvD(YRN z7f*wcA`XDH55RRYefspzcwEl z>5?Ny4z}wK_XSX#$K1})099&HPbT3#hj86mj0FvLH_+ZMHSSA;QO&G89{ViM5A2|| zgq7O``l!!M2A@;`T6Mc4B(s`LoZo`94WomiyjYW9{GN2Nnd=kCVa zJRZ$q)l8W@dGc2=ufG9!FVKd>{C^GxNq_OX55)?*^X%EP+tK#ZpwGz?lrxwKWT`f> zpLk|Fn`cm(8-+!LbbQMc|LNV*NGZNDpIua`BLHZ=PgnpjnOas9)~*s*kU|3hXO%69 z?~1V_*hc^;!^1VD@pJ3|fclrT!!RCl;v_TB0gV`68v5g6hY;T%>@3#Qi6wQ`Y0LLy zymvxFLl?;XEAj3UojP@T6?C(e`l^N*40Qs8Uk$&N@dmE;{%;|xCZb*kb~X%F3W z7M@G6rD$1>=Mv)iMp!7Ac9LR)G2{D(Lnr@Ex<+Qrk7pFYdtbye-qUzFK5Nyg)lzX8 z(k@e!iB<6THN{{^qK|-t!8Tn*-GcU{E?c(j3;Xx)-)Yllv){pDU#KodD%ySepzoc* zYzviz#1Q=f(5?6$o^Qx=71u2{)DKD(YvmR-;(X8DxS<)0(U^0v1@Y zVns*gyg-h@5jjHIdM&NsV@i}LL2L6a3s~L3e5}E$_q0c!JCH;_ZOM`)bJa8ZG#jwk zXNIiKijIz6KqH>jU#y((=P#fi-KS3dXB3^M5Tm`1>|1 z#o#PivW$ngUt3{@f+FF}fW$H~U%q@JxTTlYVOik{YJi9$`A4)`gWyKy%$YNqXCZp- zg_5RLR?Y0PO&fCfA2) z05wpo{?)}j+E(6GTEN22VxL_4_KLixy|{%++JHtfxxSHH+fhE}_UKQgEg($NTAury zFx@krxa}t}fzDs)OOP;9U9m4TlK0j3Jh)FHP}*48b3b<9q_0vUt%Tcn8vNw9>eBx7 zmS^UYd3@I6{cOKbp~;t||8|}Sr&xV;&UO1r?C!VwUbk1n*ET#F0314W=(rcr%>V#1 z*ui?5F&Oazn0Yt!ziwiHMOE3|q3VLG`sk(bpnObFJl9lF)YdM=?| zLJhlig*v<^K*l7Kv_Glm{uzq?9=H7O7YnavlNF)ASFz##wev)rG!2FnwO_M{BBq!# zS+0!|5t5?VAX!QC*bra|ac9wYKSso_Q6et(@H~7hbv9W!&11;}G~NOJ{)r%Cpga3+ zaRJ~y0HhSrv7%S8;r_L+dLG;w`I<0SYDLVcqg?ZX=v?VN_WRN^h}dR*eq#~Yp3=CA z(ujUn&PF^n#otO|@N-YKb&Fe^qh7>8s{d&@t#5I>BKhIncHOTs(K1= zu*2!VVd`g_cXIE;RWI!d_jYow<=V85Ft%#{^V?k4+pfA?^8XJ2lBQ0bI=h^nvdZCh zuBw?N@#p9900Qcvb^rL|kN?OCIKC7kW}gR_pnbC(t~0g+|MuH&!x+q@sdR%K!F#h> zYycT9UcA^wu@TVV#8Y|qNB~bx*&e%E^Uf?ulP0AR^cJ4GSo3>g`kL{ZH67WHc;4&R zuU~%}f7_zgw-~QNPMtc{P_7%T*b7p&Y15{-oTR(MuK#%M1TXGEV?5~wQ!8WoEgN9m z^8W+?Xxzy^XU-hv{bOLtbio)|E7(LiDuQD6I8;Q3=JOvlYSd^vbG_wA z>LXy-k`{0+&z*$wn{^+9pR}u4t5&T(kt4pZVMYas+cF^0Z~f+DAr47ErFE zYwq7-`%&w^<#)IIKLP++?qB3#6Mg&kU4HG_wYd(t%&Di-ugxgE&TZSab!<|lH=MD0E({}1jNlIOn$&E!Kh?9HG`_@3VGS1 zPyNN<8*ADm+BF)}U(W`_#Kf3h1S9J#BhbK{KbW62=O!3?8vuqejB7}fCQVjVs-oQk zfY)5WhwY_bJrYtwWT8TZ`UM6CRFXUfW5$fW7=y){ zai+b4E=c;;I|v}E+h$YAI91ZrA|Bt9hq~n-003NBo|-acN)(v#te>CXP^=uCWQEbC zuMQL`fW$&yJ_8Rv?9ib@M=(f?Yy@em+{2aYU7r3zX2Z2VNj$)lesZocZW2<81^w<^lgfY zw9t2Cas@C8!*2foV-D2l{R~{`34oaa9sTN#9XrnRI4K^X91swoBWB*i01-y9(KWb? zAR;$yVGl$<>^jMzESw>HVGvA?Ac zWcCWeRA!+|vxMb6=I7KJH6>V>)m>%X@;?Uv6k%V)Dts6^?rizrICU5GV~F(Jg*8Y+ z^r^6V_o5EZJZrmm?_QZKS+c$&))wHQz`%yAlC)UifeujaH+>F{}ccq6XZyn zHtqX|4eTio)90N^L$FgH3p>v?da14VdR%xeo%TMLa# z)R$?|=4OtPqKkVIfrEs3^N0?dO5UHvqu*kwssnkXx_Yl$+>$^N07d>x!jOK7D4kY- wKuv#SX1pmZE4M2ayTvVTaf@5r@}!sl1Com|Ch3@&761SM07*qoM6N<$g73;JrvLx| literal 22779 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 diff --git a/maps-editor/.classpath b/maps-editor/.classpath index 5ded48f..de9306d 100644 --- a/maps-editor/.classpath +++ b/maps-editor/.classpath @@ -1,9 +1,10 @@ - - - - - - - - - + + + + + + + + + + diff --git a/maps-editor/assets-raw/skin/check-off.png b/maps-editor/assets-raw/skin/check-off.png new file mode 100644 index 0000000000000000000000000000000000000000..14b65120c6f8c0d3c62ee00c068ace25cc2c8abd GIT binary patch literal 1123 zcmaJ=TSyd97@iQq>>h=5pGJES+nF=#?&?miraNOUIb!Q#8&o*X9NkH0&NOpuSHT|a zPSHa}QAzixe25;Tj2?<2N`j(@#0q?=Adz~BiZ0X{bFGKAfivgA_kI8O|K~q@>uOg| z&smT|QPlKEwHPJy40mKtCjX_?o+o6QkEJ-SN6k2?*pRBwP!pshhSCC~P|-TJ48T%~ z%3}0b9LMDvK}80uxEMBNScFYcrR6D0QClIVn_!D>hL{gGZ!xs4g_w1I8OT-`CiLn~ z8`gK$#?;PMHK;M=WprsuAOZ%&3Y{|AOh-tCm@!>}tleXdp~oP&HN^Zj6_@MiFtQ=- zXMslr0iF){Sl$PMzTgVl3wXfsUJev{cp&(T1;EqeA48(qTC)%ptHxuIU5H6wYzZ8f zOeWc65sT~=jt>Td9Pn~puZJK!PKSw=l*e@P(h4GUR9m;Oj!fEBRGLsb4l%^(?-UGc zLe_M~%R~ysr4)UFW2FmjPp@uPmc5oE_@%v9E(#sgZmZbHn$>eC>)xZ>czkHXo0(sJe$8qKY<;mOP?l4{jV{lh^M2Y6t?}r}BSS;E zce6T9KGct2H5S%ncT;e^x3lmlJodUL?||BWr8PewZ*SkYcF?b$?^`kTsc+}u=$gj; zA$8-RGMtRZHqDhroJG5zKjM2gpKP&42DU7k_pIR9?SXgIr%vyQ-MMkA@WODxHtGAm zv-l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83 zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0Mv>2~2MaLaz%>y`ZF!TL84#CABECEH%ZgC_h&L>}9J=+-`Bi zX&zK>3U0SJ;nb@Sbc{YIVv!;mCIn19ASOK70y*%6pPC0u?M1+39dbJUDFXvzil>WX zNX4z1pxvtuISANF2X_jX3e}h?MQ|QG)R4OGpjAic0e&yRgZ=_68@GJo7U8xQcIj5} z^+*I~)j9~OuA6VRC~ydPzO387_+rKb>5|yv87595t{Zf?m2N6?w>mKv%Ip`ENM3RM z^#v2&qe+6d--Y}sYa{VJTB(j?g4N3IRCnCth)sgW;B;ZcEw%l6wx zYwY|xC!b_l8pL`0@x;j*$LD_e{ddRZmrJhsTz+z`_P)P*rG?Cfxb>Iiu9yB7nY+F-Mc$W>RhBsOMln(D>6HthB5Z!zn`_gUHpr|Il|a#H+Bof anVlj05A)5P{1ZSr89ZJ6T-G@yGywp5i4%_i literal 0 HcmV?d00001 diff --git a/maps-editor/assets-raw/skin/default-pane.9.png b/maps-editor/assets-raw/skin/default-pane.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6b3f9042d5cc7bfeef2f63a2d50a9dedf68aab56 GIT binary patch literal 93 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)Q!3HEdXMMW?q*Odz978H@B_}vE{P&PxR8dix qp!(1GCx`63dGiW-7D>_E)Q!3HEdXMMW?q*Odz978H@B_}vE{P&PxR8dix qp!(1GCx`63dGiW-7D>s?pa1{> literal 0 HcmV?d00001 diff --git a/maps-editor/assets-raw/skin/default-round-large.9.png b/maps-editor/assets-raw/skin/default-round-large.9.png new file mode 100644 index 0000000000000000000000000000000000000000..ffc12fd693a683dbcead201cf8bbd1fc0b7f7a29 GIT binary patch literal 404 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 R#V7y(002ovPDHLkV1m{o#$f;e literal 0 HcmV?d00001 diff --git a/maps-editor/assets-raw/skin/default-slider-knob.png b/maps-editor/assets-raw/skin/default-slider-knob.png new file mode 100644 index 0000000000000000000000000000000000000000..e03d3749d805feb1d171a055aa8bb55c93ed1c54 GIT binary patch literal 992 zcmaJ=O>5LZ7*1PJyV!%`!H>(3f+shb#NBKY8tXRMb|a>&+l4OlV6(|=H@3;dWa_4+ zXw}PB`U4ac3WCssA4kQ5M-c?Ui(oI}$(t7s9)vpC?ruG}1}5{t^E~hKzVpszX=ZvP zbtc6y%!pCc%XA*5_koPRKwxBcBH%6d*5%Jj|s9sAO{r6hP-=X|&L(D`kDE6N~Otwn0ds@O-n`a z94_*LEXzC;c~MMLL^@jai4~{)Xtb-KqsR{3fVkKPiK11*D@0|f)BO~@peO4`oifpa z@v#-~0tb_nxP2^04*aPj96}P{ad|$l|*Xi$N zvM-mXX87b{|GMbz5$&F2*=t+6=`}v`X}5>8t*!Y_x9O9p7<#@Ef2lbO3%@>IVqR{v zwziHvO4UC+7`S?9@bimn?@HS{JHt;Oo@y~q?tg#QI(~WYNI?vL-}yFj@^$h0*VNwb m?(RKz-DqtOzM3Qh8_W=M?(y06Y2#Kpd2>c#Mt?rJaQ6=loH$qj literal 0 HcmV?d00001 diff --git a/maps-editor/assets-raw/skin/default-slider.9.png b/maps-editor/assets-raw/skin/default-slider.9.png new file mode 100644 index 0000000000000000000000000000000000000000..5d7a781fad146c40fbced3010e1e940166b20ae1 GIT binary patch literal 103 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4>3O<1hE&{2PH6tValQZ z-@bj*kcj_E)Q!3HEdXMMW?q@+Au978H@B_}8_{JVYoc5|V# hGxHqZohp6|3~!a?y@TEUOaZE5@O1TaS?83{1OPAP7Y_gc literal 0 HcmV?d00001 diff --git a/maps-editor/assets-raw/skin/default-window.9.png b/maps-editor/assets-raw/skin/default-window.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0db9e673372d33b5b64ba70cfa464e31c7c71f30 GIT binary patch literal 316 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 literal 0 HcmV?d00001 diff --git a/maps-editor/assets-raw/skin/pack.json b/maps-editor/assets-raw/skin/pack.json new file mode 100644 index 0000000..749d207 --- /dev/null +++ b/maps-editor/assets-raw/skin/pack.json @@ -0,0 +1,5 @@ +{ +duplicatePadding: false, +paddingX: 1, +paddingY: 1 +} \ No newline at end of file diff --git a/maps-editor/assets-raw/skin/selection.png b/maps-editor/assets-raw/skin/selection.png new file mode 100644 index 0000000000000000000000000000000000000000..d2533cb0d1e63aab296d61dd504bdb62bd59fb74 GIT binary patch literal 922 zcmaJ=J#W)M7zq8!^7)PDu9mje`px)(1{q(JHP)F zPDZPkq5)0Vl6&&vrYCu**ywQT3dTo_T^CjC&zWG!oW-Cstf~ZD29eLwWP^7c!;TW+ zMjo{-6G{a&2z;Yq5v^Wnm8}}agmiJE+}5qS-mR6Zi!P32_vFi6+se(Z@o6Nx uoy)d<^6BkY`6M3NR%?{~m|T7Q^576F2d|XhAKoiB`D?MeJ?nG(>h&K(z!-%9 literal 0 HcmV?d00001 diff --git a/maps-editor/assets-raw/skin/textfield.9.png b/maps-editor/assets-raw/skin/textfield.9.png new file mode 100644 index 0000000000000000000000000000000000000000..cb8ad314f455c26c219fee5e0c0adc6680110e44 GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`hdo^!Ln>}fo$SrkYQW`;&VLR*vemXvd0X!9Gh4ZzuX)Xt%+vhH-+-~I zn!_hyUb@fa5~qLCJ`MjASkw%7oCSOqy#K8*!{K^q+^ub?ciKF&doB9z&ZukbTD@wQ zzcTA&701YFaZ`fy+S$(U_Q~&dG4cJpZ{osl%$etbX0(@W0eXkQ)78&qol`;+0BhD~ AH~;_u literal 0 HcmV?d00001 diff --git a/maps-editor/assets-raw/skin/tree-minus.png b/maps-editor/assets-raw/skin/tree-minus.png new file mode 100644 index 0000000000000000000000000000000000000000..f8e4079319c6f2cc5c46b10075ea7de917a5dae7 GIT binary patch literal 328 zcmV-O0k{5%P)PbXFRCwB?QoYK9KoFb@2a274U}0rr z^1AE-DJ2yM*GTf1-shH;(`@Ta5*L6`< z6{EX6k|YtFa|A&E%d)h@wr#O(8=9tJjFi@OMHEF4LMRE0u}7}&dmP7s3$^F_zI>eL zsf^bgb+s%DIC(NnQzlhPI&eBvq`x4E)B<@ZisEB&p6A~wzScby30ao?uhpMC>Y$tL zB@9D!T_;sl@jlaG7~nY0ZRO%P7WA`i+gfFs=`>9+3 z1zcTxTQ|YMQD4BxMK?EH{22sIduc0zrnJxx?zrU3m%B?2;e{Xw_%IBxEDOkBnkE!Q zffNhp`#xukf#W!!X_{16*EP)Z3{_PjQl@E|z;#{VoL>qMap&5$g>_xwM&;SIEf(9h z?H0$eFp7^HRW*(yFzQKFRY6{X4qt(hlZifY?0qZskf!PBK$0YT97WOh8D-hov5zne zcOuKO)82o3{8fmLQh3ue;(xwD*L7eR#&hI6&*Su}x~@wFG(COagQ6%S?=U~&qt&h!s>Ucd}978x}Cja>V|9?FL=Wph}92Z~6 Q0VNnbUHx3vIVCg!03Qx8{Qv*} literal 0 HcmV?d00001 diff --git a/maps-editor/src/BuildAssets.java b/maps-editor/src/BuildAssets.java new file mode 100644 index 0000000..4cef110 --- /dev/null +++ b/maps-editor/src/BuildAssets.java @@ -0,0 +1,11 @@ +import com.badlogic.gdx.tools.imagepacker.TexturePacker2; + + +public class BuildAssets { + + public static void main( String[] args ) { + +// Settings settings = new Settings(); + TexturePacker2.process( "assets-raw/skin", "../maps-editor-android/assets/data", "uiskin.atlas" ); + } +} diff --git a/maps-editor/src/com/mobidevelop/maps/editor/LevelStage.java b/maps-editor/src/com/mobidevelop/maps/editor/LevelStage.java new file mode 100644 index 0000000..0c2ce1f --- /dev/null +++ b/maps-editor/src/com/mobidevelop/maps/editor/LevelStage.java @@ -0,0 +1,227 @@ +package com.mobidevelop.maps.editor; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input.Buttons; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Pixmap.Format; +import com.badlogic.gdx.graphics.Texture.TextureFilter; +import com.badlogic.gdx.graphics.g2d.PixmapPacker; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.Group; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.ui.Image; +import com.badlogic.gdx.utils.SnapshotArray; +import com.mobidevelop.maps.MapLayer; +import com.mobidevelop.maps.MapObject; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerAddedEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerHiddenEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerRemovedEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerSelectedEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerShownEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapObjectsModel.ObjectAddedEvent; +import com.mobidevelop.maps.editor.ui.UIEvents; +import com.mobidevelop.utils.events.Event; +import com.mobidevelop.utils.events.EventDispatcher; +import com.mobidevelop.utils.events.EventListener; + + +public class LevelStage extends Stage implements EventListener { + + + private PixmapPacker imagePacker = null; + private TextureAtlas imageAtlas; + + private Group layers; + + private OrthographicCamera camera; + private Vector2 worldspaceCenter = new Vector2(); + int lastX, lastY; + Vector3 vec3 = new Vector3(); + + public LevelStage( float width, float height, SpriteBatch batch ) { + + super( width, height, true, batch ); + + // imagePacker packs added image resources into an atlas for stage rendering + imagePacker = new PixmapPacker( 1024, 1024, Format.RGBA8888, 2, false ); + imageAtlas = new TextureAtlas(); + + layers = new Group(); + addActor( layers ); + + camera = new OrthographicCamera( width, height ); + setCamera( camera ); + } + + // handles camera controls + @Override + public boolean touchDown( int screenX, int screenY, int pointer, int button ) { + + lastX = screenX; + lastY = screenY; + if ( Gdx.input.isButtonPressed( Buttons.LEFT ) ) { + Actor selected = layers.hit( screenX, screenY, false ); + if ( selected != null ) { + if ( selected instanceof StageObject ) + selected.fire( UIEvents.selectObject( ((StageObject)selected).getMapObject() ) ); + } + } + return super.touchDown( screenX, screenY, pointer, button ); + } + + // handles camera controls + @Override + public boolean touchDragged( int screenX, int screenY, int pointer ) { + + if ( Gdx.input.isButtonPressed( Buttons.RIGHT ) ) { + camera.position.sub( screenX - lastX, lastY - screenY, 0 ); + lastX = screenX; + lastY = screenY; + updateCamera(); + } + + return super.touchDragged( screenX, screenY, pointer ); + } + + // updates the camera and stores the stage center for use with MapObject addition to the stage + private void updateCamera() { + + camera.update(); + camera.unproject( vec3.set( 0.5f*Gdx.graphics.getWidth() + camera.position.x / Gdx.graphics.getWidth(), + 0.5f*Gdx.graphics.getHeight() + ( Gdx.graphics.getHeight() - camera.position.y ) / Gdx.graphics.getHeight(), 0 ) ); + worldspaceCenter.set( vec3.x, vec3.y ); + } + + public Vector2 getCenter() { + return worldspaceCenter; + } + + // adds an image brush to the stage, packing it into the atlas if not already there + public void addImageBrush( MapLayer layer, MapObject object, FileHandle file ) { + + // look for image in atlas, pack if not found + AtlasRegion image = imageAtlas.findRegion( file.name() ); + if ( image == null ) { + imagePacker.pack( file.name(), new Pixmap( file ) ); + imagePacker.updateTextureAtlas( imageAtlas, TextureFilter.Linear, TextureFilter.Linear, true ); + image = imageAtlas.findRegion( file.name() ); + } + + // add image to stage + ImageBrush brush = new ImageBrush( image, object ); + brush.setPosition( object.getX(), object.getY() ); + Group layerGroup = getLayerGroup( layer ); + if ( layerGroup != null ) + layerGroup.addActor( brush ); + } + + private LayerGroup getLayerGroup( MapLayer layer ) { + + SnapshotArray ls = layers.getChildren(); + for ( int i = 0; i < ls.size; i++ ) { + LayerGroup l = (LayerGroup)ls.get( i ); + if ( layer == l.getLayer() ) + return l; + } + return null; + } + + @Override + public void dispose() { + + super.dispose(); + imagePacker.dispose(); + } + + @Override + public void onEvent( EventDispatcher dispatcher, Event event ) { + + // model added a layer + if ( event instanceof LayerAddedEvent ) { + LayerAddedEvent evt = (LayerAddedEvent)event; + LayerGroup layer = new LayerGroup( evt.layer ); + layer.setName( evt.layer.getName() ); + layers.addActor( layer ); + return; + } + + // model removed a layer + if ( event instanceof LayerRemovedEvent ) { + LayerRemovedEvent evt = (LayerRemovedEvent)event; + LayerGroup layer = getLayerGroup( evt.layer ); + if ( layer != null ) + layer.remove(); + return; + } + + // model changed selected layer + if ( event instanceof LayerSelectedEvent ) { + return; + } + + // model hid layer + if ( event instanceof LayerHiddenEvent ) { + LayerHiddenEvent evt = (LayerHiddenEvent)event; + LayerGroup layer = getLayerGroup( evt.layer ); + if ( layer != null ) + layer.setVisible( false ); + return; + } + + // model showed layer + if ( event instanceof LayerShownEvent ) { + LayerShownEvent evt = (LayerShownEvent)event; + LayerGroup layer = getLayerGroup( evt.layer ); + if ( layer != null ) + layer.setVisible( true ); + return; + } + + // model added an image brush + if ( event instanceof ObjectAddedEvent ) { + ObjectAddedEvent evt = (ObjectAddedEvent)event; + addImageBrush( evt.layer, evt.object, evt.file ); + } + } + + public class LayerGroup extends Group { + + private MapLayer layer; + public LayerGroup( MapLayer layer ) { + + this.layer = layer; + } + + public MapLayer getLayer() { + + return layer; + } + } + + public interface StageObject { + public MapObject getMapObject(); + } + + public class ImageBrush extends Image implements StageObject { + + private MapObject object; + + public ImageBrush( AtlasRegion image, MapObject object ) { + + super( image ); + this.object = object; + } + + @Override + public MapObject getMapObject() { + return object; + } + } +} diff --git a/maps-editor/src/com/mobidevelop/maps/editor/MapEditor.java b/maps-editor/src/com/mobidevelop/maps/editor/MapEditor.java index 875c6b4..a4f41ee 100644 --- a/maps-editor/src/com/mobidevelop/maps/editor/MapEditor.java +++ b/maps-editor/src/com/mobidevelop/maps/editor/MapEditor.java @@ -17,31 +17,267 @@ package com.mobidevelop.maps.editor; import com.badlogic.gdx.ApplicationListener; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.InputMultiplexer; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.GL10; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.scenes.scene2d.Event; +import com.badlogic.gdx.scenes.scene2d.EventListener; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.ui.Skin; +import com.badlogic.gdx.scenes.scene2d.ui.SplitPane; +import com.badlogic.gdx.scenes.scene2d.ui.Table; +import com.badlogic.gdx.scenes.scene2d.ui.TextButton; +import com.badlogic.gdx.scenes.scene2d.utils.Align; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Disposable; +import com.mobidevelop.maps.MapLayer; +import com.mobidevelop.maps.MapObject; +import com.mobidevelop.maps.basic.BasicMap; +import com.mobidevelop.maps.brush.ImageBrushMapObject; +import com.mobidevelop.maps.editor.models.MapModels.MapModel; +import com.mobidevelop.maps.editor.ui.FileDialog; +import com.mobidevelop.maps.editor.ui.ImagePicker; +import com.mobidevelop.maps.editor.ui.LayerList; +import com.mobidevelop.maps.editor.ui.SlideWindow; +import com.mobidevelop.maps.editor.ui.UIEvents; +import com.mobidevelop.maps.editor.ui.UIEvents.FileEvent; +import com.mobidevelop.maps.editor.ui.UIEvents.LayerEvent; +import com.mobidevelop.maps.editor.ui.UIEvents.MapObjectEvent; +import com.mobidevelop.maps.editor.ui.UIEvents.UIEvent; -public class MapEditor implements ApplicationListener { +public class MapEditor implements ApplicationListener, EventListener { + + private SpriteBatch batch; + + private Skin skin; + private Stage uiStage; + private LevelStage worldViewport; + + private SlideWindow fileWindow; + private SlideWindow imageWindow; + + private ImagePicker imagePicker; + private LayerList layerList; + private FileDialog fileDialog; + + private MapModel mapModel; + + private Array trash; + + private String projectImagePath; @Override public void create() { + + float w = Gdx.graphics.getWidth(); + float h = Gdx.graphics.getHeight(); + + batch = new SpriteBatch(); + skin = new Skin( Gdx.files.internal( "data/uiskin.json" ) ); + + fileDialog = new FileDialog( "Set Image Path...", Gdx.files.external( "" ), skin ); + + // File Window ******************************************************************************** + + final TextButton openProjectButton = new TextButton( "Set Image Path", skin ); + openProjectButton.addListener( new ClickListener() { + @Override + public void clicked( InputEvent event, float x, float y ) { + + fileDialog.editFileName( false ); + uiStage.addActor( fileDialog ); + super.clicked( event, x, y ); + } + } ); + fileWindow = new SlideWindow( skin, Align.top, 10, 200 ); + fileWindow.add( openProjectButton ); + + // Image Window ******************************************************************************* + // holds the image picker and layer list + + imagePicker = new ImagePicker( skin ); + layerList = new LayerList( skin ); + Table pickerTable = new Table(); + pickerTable.add( imagePicker ).pad( 10 ); + Table layerTable = new Table(); + layerTable.add( layerList ).pad( 10 ); + SplitPane splitPane = new SplitPane( pickerTable, layerTable, true, skin ); + imageWindow = new SlideWindow( skin, Align.right, 10, 200 ); + imageWindow.add( splitPane ).expand().fill().pad( 10 ); + + // UI Stage *********************************************************************************** + // holds the menu overlay, with sliding windows for different tool palettes + + uiStage = new Stage( w, h, true, batch ){ + @Override + public boolean touchDown( int screenX, int screenY, int pointer, int button ) { + + if ( screenY < 20 ) + fileWindow.showWindow(); + else if ( screenX > Gdx.graphics.getWidth() - 20 ) + imageWindow.showWindow(); + + return super.touchDown( screenX, screenY, pointer, button ); + } + }; + uiStage.addListener( this ); + uiStage.addActor( fileWindow ); + uiStage.addActor( imageWindow ); + + // World Viewport ***************************************************************************** + // the stage where the map is drawn + + worldViewport = new LevelStage( w, h, batch ); + + createNewMap(); + + Gdx.input.setInputProcessor( new InputMultiplexer( uiStage, worldViewport ) ); + + trash = new Array(); + trash.add( imagePicker ); + trash.add( batch ); + trash.add( skin ); + trash.add( uiStage ); + trash.add( worldViewport ); + + loadSettings(); + } + + private void createNewMap() { + + mapModel = new MapModel( new BasicMap() ); + mapModel.addEventListener( worldViewport ); + mapModel.addEventListener( layerList ); + + // add default layer + mapModel.addLayer(); + mapModel.setCurrentLayer( mapModel.currentLayer() ); } @Override public void pause() { + + saveSettings(); } @Override public void resume() { } - + + private void loadSettings() { + + Settings.load(); + projectImagePath = Settings.getLastImagePath(); + if ( projectImagePath != null ) + imagePicker.loadProjectImages( Gdx.files.external( projectImagePath ) ); + } + + private void saveSettings() { + + Settings.setLastImagePath( projectImagePath ); + Settings.save(); + } + @Override - public void resize(int width, int height) { + public void resize( int width, int height ) { } @Override public void render() { + + Gdx.gl.glClearColor( 0, 0, 0, 1 ); + Gdx.gl.glClear( GL10.GL_COLOR_BUFFER_BIT ); + + worldViewport.act(); + worldViewport.draw(); + + uiStage.act(); + uiStage.draw(); } @Override public void dispose() { + + for ( int i = 0; i < trash.size; i++ ) { + Disposable d = trash.get( i ); + if ( d != null ) + d.dispose(); + } + trash.clear(); } + /** + * handles events sent by ui components to interact with the model + */ + @Override + public boolean handle( Event event ) { + + if ( event instanceof UIEvent ) { + + MapLayer layer; + MapObject mapObject; + switch ( ((UIEvent)event).type ) { + case UIEvents.EV_ADD_LAYER: + mapModel.addLayer(); + break; + + case UIEvents.EV_REMOVE_LAYER: + // can't remove last layer + if ( mapModel.numLayers() > 1 ) + mapModel.removeLayer( mapModel.currentLayer() ); + break; + + case UIEvents.EV_SELECT_LAYER: + layer = ((LayerEvent)event).layer; + mapModel.setCurrentLayer( layer ); + break; + + case UIEvents.EV_HIDE_LAYER: + layer = ((LayerEvent)event).layer; + mapModel.hideLayer( layer ); + break; + + case UIEvents.EV_SHOW_LAYER: + layer = ((LayerEvent)event).layer; + mapModel.showLayer( layer ); + break; + + case UIEvents.EV_ADD_IMAGE: + FileEvent eai = (FileEvent)event; + FileHandle file = eai.file; + layer = mapModel.currentLayer(); + Vector2 v = worldViewport.getCenter(); + mapModel.addImageBrush( layer, new ImageBrushMapObject( layer, v.x, v.y, file.name() ), file ); + break; + + case UIEvents.EV_SELECT_MAP_OBJECT: + MapObjectEvent moe = (MapObjectEvent)event; + mapObject = moe.object; + break; + + case UIEvents.EV_SELECT_IMAGE_PATH: + FileEvent esip = (FileEvent)event; + + // ensure folder + FileHandle folder = esip.file; + if ( !folder.isDirectory() ) + folder = folder.parent(); + + projectImagePath = folder.path(); + imagePicker.loadProjectImages( esip.file ); + break; + + default: + break; + } + return true; + } + + return false; + } } diff --git a/maps-editor/src/com/mobidevelop/maps/editor/Settings.java b/maps-editor/src/com/mobidevelop/maps/editor/Settings.java new file mode 100644 index 0000000..de351f5 --- /dev/null +++ b/maps-editor/src/com/mobidevelop/maps/editor/Settings.java @@ -0,0 +1,27 @@ +package com.mobidevelop.maps.editor; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Preferences; + + +public class Settings { + + private static Preferences prefs; + + public static void load() { + prefs = Gdx.app.getPreferences( "settings" ); + } + + public static void save() { + if ( prefs != null ) + prefs.flush(); + } + + public static String getLastImagePath() { + return prefs.getString( "lastImagePath" ); + } + + public static void setLastImagePath( String imagePath ) { + prefs.putString( "lastImagePath", imagePath ); + } +} diff --git a/maps-editor/src/com/mobidevelop/maps/editor/commands/MapCommands.java b/maps-editor/src/com/mobidevelop/maps/editor/commands/MapCommands.java index e3274cb..16fa567 100644 --- a/maps-editor/src/com/mobidevelop/maps/editor/commands/MapCommands.java +++ b/maps-editor/src/com/mobidevelop/maps/editor/commands/MapCommands.java @@ -18,6 +18,7 @@ import com.mobidevelop.maps.Map; import com.mobidevelop.utils.commands.Command; +import com.mobidevelop.utils.events.Event; public class MapCommands { @@ -50,15 +51,17 @@ public MoveMapCommand(Map map, float newX, float newY) { } @Override - public void execute() { + public Event execute() { map.setX(newX); - map.setY(newY); + map.setY(newY); + return null; } @Override - public void reverse() { + public Event reverse() { map.setX(oldX); map.setY(oldY); + return null; } } @@ -80,15 +83,17 @@ public ResizeMapCommand(Map map, int newWidth, int newHeight) { } @Override - public void execute() { + public Event execute() { map.setWidth(newWidth); - map.setHeight(newHeight); + map.setHeight(newHeight); + return null; } @Override - public void reverse() { + public Event reverse() { map.setWidth(oldWidth); map.setHeight(oldHeight); + return null; } } @@ -106,13 +111,15 @@ public RenameMapCommand(Map map, String newName) { } @Override - public void execute() { + public Event execute() { map.setName(newName); + return null; } @Override - public void reverse() { + public Event reverse() { map.setName(oldName); + return null; } } diff --git a/maps-editor/src/com/mobidevelop/maps/editor/commands/MapLayerCommands.java b/maps-editor/src/com/mobidevelop/maps/editor/commands/MapLayerCommands.java index 9903d2b..6c60b7a 100644 --- a/maps-editor/src/com/mobidevelop/maps/editor/commands/MapLayerCommands.java +++ b/maps-editor/src/com/mobidevelop/maps/editor/commands/MapLayerCommands.java @@ -17,7 +17,10 @@ package com.mobidevelop.maps.editor.commands; import com.mobidevelop.maps.MapLayer; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerHiddenEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerShownEvent; import com.mobidevelop.utils.commands.Command; +import com.mobidevelop.utils.events.Event; public final class MapLayerCommands { @@ -27,6 +30,10 @@ public static HideLayerCommand hide(MapLayer layer) { return new HideLayerCommand(layer); } + public static ShowLayerCommand show(MapLayer layer) { + return new ShowLayerCommand(layer); + } + public static MoveLayerCommand move(MapLayer layer, int newX, int newY) { return new MoveLayerCommand(layer, newX, newY); } @@ -39,10 +46,6 @@ public static RenameLayerCommand rename(MapLayer layer, String newName) { return new RenameLayerCommand(layer, newName); } - public static ShowLayerCommand show(MapLayer layer) { - return new ShowLayerCommand(layer); - } - public static class HideLayerCommand implements Command { private MapLayer layer; @@ -52,17 +55,39 @@ public HideLayerCommand(MapLayer layer) { } @Override - public void execute() { + public Event execute() { layer.setVisible(false); + return new LayerHiddenEvent( layer ); } @Override - public void reverse() { + public Event reverse() { layer.setVisible(true); + return new LayerShownEvent( layer ); + } + } + + public static class ShowLayerCommand implements Command { + + private MapLayer layer; + + public ShowLayerCommand(MapLayer layer) { + this.layer = layer; } + @Override + public Event execute() { + layer.setVisible(true); + return new LayerShownEvent( layer ); + } + + @Override + public Event reverse() { + layer.setVisible(false); + return new LayerHiddenEvent( layer ); + } } - + public static class MoveLayerCommand implements Command { private MapLayer layer; @@ -80,15 +105,17 @@ public MoveLayerCommand(MapLayer layer, int newX, int newY) { } @Override - public void execute() { + public Event execute() { layer.setX(newX); - layer.setY(newY); + layer.setY(newY); + return null; } @Override - public void reverse() { + public Event reverse() { layer.setX(oldX); layer.setY(oldY); + return null; } } @@ -110,15 +137,17 @@ public ResizeLayerCommand(MapLayer layer, int newWidth, int newHeight) { } @Override - public void execute() { + public Event execute() { layer.setWidth(newWidth); - layer.setHeight(newHeight); + layer.setHeight(newHeight); + return null; } @Override - public void reverse() { + public Event reverse() { layer.setWidth(oldWidth); layer.setHeight(oldHeight); + return null; } } @@ -136,35 +165,16 @@ public RenameLayerCommand(MapLayer layer, String newName) { } @Override - public void execute() { + public Event execute() { layer.setName(newName); + return null; } @Override - public void reverse() { + public Event reverse() { layer.setName(oldName); + return null; } } - - public static class ShowLayerCommand implements Command { - - private MapLayer layer; - - public ShowLayerCommand(MapLayer layer) { - this.layer = layer; - } - - @Override - public void execute() { - layer.setVisible(true); - } - - @Override - public void reverse() { - layer.setVisible(false); - } - - } - } diff --git a/maps-editor/src/com/mobidevelop/maps/editor/commands/MapLayersCommands.java b/maps-editor/src/com/mobidevelop/maps/editor/commands/MapLayersCommands.java index 9b7b94b..89cd8f3 100644 --- a/maps-editor/src/com/mobidevelop/maps/editor/commands/MapLayersCommands.java +++ b/maps-editor/src/com/mobidevelop/maps/editor/commands/MapLayersCommands.java @@ -19,14 +19,18 @@ import com.mobidevelop.maps.Map; import com.mobidevelop.maps.MapLayer; import com.mobidevelop.maps.MapLayers; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerAddedEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerRemovedEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerSwappedEvent; import com.mobidevelop.utils.commands.Command; +import com.mobidevelop.utils.events.Event; final public class MapLayersCommands { private MapLayersCommands() { } - public static AddLayerCommand add(Map map, MapLayer layer) { - return new AddLayerCommand(map, layer); + public static AddLayerCommand add(Map map, String layerName) { + return new AddLayerCommand(map, layerName); } public static InsertLayerCommand insert(Map map, int index, MapLayer layer) { @@ -43,23 +47,26 @@ public static SwapLayersCommand swap(Map map, MapLayer layer1, MapLayer layer2) public static class AddLayerCommand implements Command { private Map map; + private String layerName; private MapLayer layer; - public AddLayerCommand(Map map, MapLayer layer) { + public AddLayerCommand(Map map, String name) { this.map = map; - this.layer = layer; + this.layerName = name; } @Override - public void execute() { - map.getLayers().addLayer(layer); + public Event execute() { + layer = map.createLayer( layerName ); + map.getLayers().addLayer( layer ); + return new LayerAddedEvent( layer ); } @Override - public void reverse() { - map.getLayers().removeLayer(layer); + public Event reverse() { + map.getLayers().removeLayer( layer ); + return new LayerRemovedEvent( layer ); } - } public static class InsertLayerCommand implements Command { @@ -76,13 +83,15 @@ public InsertLayerCommand(Map map, int index, MapLayer layer) { } @Override - public void execute() { + public Event execute() { map.getLayers().addLayer(index, layer); + return null; } @Override - public void reverse() { + public Event reverse() { map.getLayers().removeLayer(layer); + return null; } } @@ -98,17 +107,20 @@ public RemoveLayerCommand(Map map, MapLayer layer) { this.map = map; this.layer = layer; } - + @Override - public void execute() { + public Event execute() { MapLayers layers = map.getLayers(); - index = layers.getIndex(layer); - layers.removeLayer(index); + index = layers.getIndex( layer ); + layers.removeLayer( layer ); + return new LayerRemovedEvent( layer ); } + @Override - public void reverse() { + public Event reverse() { MapLayers layers = map.getLayers(); - layers.addLayer(index, layer); + layers.addLayer( index, layer ); + return new LayerAddedEvent( layer ); } } @@ -130,15 +142,17 @@ public SwapLayersCommand(Map map, MapLayer layer1, MapLayer layer2) { this.layer1 = layer1; this.layer2 = layer2; } - + @Override - public void execute() { - map.getLayers().swapLayers(layer1, layer2); + public Event execute() { + map.getLayers().swapLayers( layer1, layer2 ); + return new LayerSwappedEvent( layer1, layer2 ); } @Override - public void reverse() { + public Event reverse() { map.getLayers().swapLayers(layer1, layer2); + return new LayerSwappedEvent( layer1, layer2 ); } } diff --git a/maps-editor/src/com/mobidevelop/maps/editor/commands/MapObjectCommands.java b/maps-editor/src/com/mobidevelop/maps/editor/commands/MapObjectCommands.java index 0e74cd6..25c25dc 100644 --- a/maps-editor/src/com/mobidevelop/maps/editor/commands/MapObjectCommands.java +++ b/maps-editor/src/com/mobidevelop/maps/editor/commands/MapObjectCommands.java @@ -18,6 +18,7 @@ import com.mobidevelop.maps.MapObject; import com.mobidevelop.utils.commands.Command; +import com.mobidevelop.utils.events.Event; public final class MapObjectCommands { @@ -27,11 +28,11 @@ public static HideObjectCommand hide(MapObject object) { return new HideObjectCommand(object); } - public static MoveObjectCommand move(MapObject object, int newX, int newY) { + public static MoveObjectCommand move(MapObject object, float newX, float newY) { return new MoveObjectCommand(object, newX, newY); } - public static ResizeObjectCommand resize(MapObject object, int newWidth, int newHeight) { + public static ResizeObjectCommand resize(MapObject object, float newWidth, float newHeight) { return new ResizeObjectCommand(object, newWidth, newHeight); } @@ -52,13 +53,15 @@ public HideObjectCommand(MapObject object) { } @Override - public void execute() { + public Event execute() { object.setVisible(false); + return null; } @Override - public void reverse() { + public Event reverse() { object.setVisible(true); + return null; } } @@ -71,7 +74,7 @@ public static class MoveObjectCommand implements Command { private float newX; private float newY; - public MoveObjectCommand(MapObject object, int newX, int newY) { + public MoveObjectCommand(MapObject object, float newX, float newY) { this.object = object; this.oldX = object.getX(); this.oldY = object.getY(); @@ -80,15 +83,17 @@ public MoveObjectCommand(MapObject object, int newX, int newY) { } @Override - public void execute() { + public Event execute() { object.setX(newX); - object.setY(newY); + object.setY(newY); + return null; } @Override - public void reverse() { + public Event reverse() { object.setX(oldX); object.setY(oldY); + return null; } } @@ -101,7 +106,7 @@ public static class ResizeObjectCommand implements Command { private float newWidth; private float newHeight; - public ResizeObjectCommand(MapObject object, int newWidth, int newHeight) { + public ResizeObjectCommand(MapObject object, float newWidth, float newHeight) { this.object = object; this.oldWidth = object.getWidth(); this.oldHeight = object.getHeight(); @@ -110,15 +115,17 @@ public ResizeObjectCommand(MapObject object, int newWidth, int newHeight) { } @Override - public void execute() { + public Event execute() { object.setWidth(newWidth); object.setHeight(newHeight); + return null; } @Override - public void reverse() { + public Event reverse() { object.setWidth(oldWidth); object.setHeight(oldHeight); + return null; } } @@ -136,13 +143,15 @@ public RenameObjectCommand(MapObject object, String newName) { } @Override - public void execute() { + public Event execute() { object.setName(newName); + return null; } @Override - public void reverse() { + public Event reverse() { object.setName(oldName); + return null; } } @@ -156,13 +165,15 @@ public ShowObjectCommand(MapObject object) { } @Override - public void execute() { + public Event execute() { object.setVisible(true); + return null; } @Override - public void reverse() { + public Event reverse() { object.setVisible(false); + return null; } } diff --git a/maps-editor/src/com/mobidevelop/maps/editor/commands/MapObjectsCommands.java b/maps-editor/src/com/mobidevelop/maps/editor/commands/MapObjectsCommands.java index bbb70a3..fe1b645 100644 --- a/maps-editor/src/com/mobidevelop/maps/editor/commands/MapObjectsCommands.java +++ b/maps-editor/src/com/mobidevelop/maps/editor/commands/MapObjectsCommands.java @@ -16,25 +16,30 @@ package com.mobidevelop.maps.editor.commands; +import com.badlogic.gdx.files.FileHandle; import com.mobidevelop.maps.MapLayer; import com.mobidevelop.maps.MapObject; import com.mobidevelop.maps.MapObjects; +import com.mobidevelop.maps.editor.models.MapModels.MapObjectsModel.ObjectAddedEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapObjectsModel.ObjectRemovedEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapObjectsModel.ObjectSwappedEvent; import com.mobidevelop.utils.commands.Command; +import com.mobidevelop.utils.events.Event; public final class MapObjectsCommands { private MapObjectsCommands() { } - public static AddObjectCommand add(MapLayer layer, MapObject object) { - return new AddObjectCommand(layer, object); + public static AddObjectCommand add(MapLayer layer, MapObject object, FileHandle file) { + return new AddObjectCommand(layer, object, file); } public static InsertObjectCommand insert(MapLayer layer, int index, MapObject object) { return new InsertObjectCommand(layer, index, object); } - public static RemoveObjectCommand remove(MapLayer layer, MapObject object) { - return new RemoveObjectCommand(layer, object); + public static RemoveObjectCommand remove(MapLayer layer, MapObject object, FileHandle file) { + return new RemoveObjectCommand(layer, object, file); } public static SwapObjectsCommand swap(MapLayer layer, MapObject object1, MapObject object2) { @@ -45,20 +50,24 @@ public static class AddObjectCommand implements Command { private MapLayer layer; private MapObject object; + private FileHandle file; - public AddObjectCommand(MapLayer layer, MapObject object) { + public AddObjectCommand(MapLayer layer, MapObject object, FileHandle file) { this.layer = layer; this.object = object; + this.file = file; } @Override - public void execute() { + public Event execute() { layer.getObjects().addObject(object); + return new ObjectAddedEvent( layer, object, file ); } @Override - public void reverse() { + public Event reverse() { layer.getObjects().removeObject(object); + return new ObjectRemovedEvent( layer, object ); } } @@ -77,13 +86,15 @@ public InsertObjectCommand(MapLayer layer, int index, MapObject object) { } @Override - public void execute() { + public Event execute() { layer.getObjects().addObject(index, object); + return null; } @Override - public void reverse() { + public Event reverse() { layer.getObjects().removeObject(object); + return null; } } @@ -91,26 +102,29 @@ public void reverse() { public static class RemoveObjectCommand implements Command { private MapLayer layer; - private MapObject object; + private FileHandle file; private int index; - public RemoveObjectCommand(MapLayer layer, MapObject object) { + public RemoveObjectCommand(MapLayer layer, MapObject object, FileHandle file ) { this.layer = layer; this.object = object; + this.file = file; } @Override - public void execute() { + public Event execute() { MapObjects objects = layer.getObjects(); index = objects.getIndex(object); objects.removeObject(index); + return new ObjectRemovedEvent( layer, object ); } @Override - public void reverse() { + public Event reverse() { MapObjects objects = layer.getObjects(); objects.addObject(index, object); + return new ObjectAddedEvent( layer, object, file ); } } @@ -135,14 +149,16 @@ public SwapObjectsCommand(MapLayer layer, MapObject object1, MapObject object2) } @Override - public void execute() { + public Event execute() { layer.getObjects().swapObjects(object1, object2); + return new ObjectSwappedEvent( object1, object2 ); } @Override - public void reverse() { + public Event reverse() { layer.getObjects().swapObjects(object1, object2); + return new ObjectSwappedEvent( object1, object2 ); } - + } } diff --git a/maps-editor/src/com/mobidevelop/maps/editor/models/MapModels.java b/maps-editor/src/com/mobidevelop/maps/editor/models/MapModels.java index ffad80f..44abc12 100644 --- a/maps-editor/src/com/mobidevelop/maps/editor/models/MapModels.java +++ b/maps-editor/src/com/mobidevelop/maps/editor/models/MapModels.java @@ -19,8 +19,13 @@ import java.util.ArrayList; import java.util.List; +import com.badlogic.gdx.files.FileHandle; import com.mobidevelop.maps.Map; import com.mobidevelop.maps.MapLayer; +import com.mobidevelop.maps.MapObject; +import com.mobidevelop.maps.editor.commands.MapLayerCommands; +import com.mobidevelop.maps.editor.commands.MapLayersCommands; +import com.mobidevelop.maps.editor.commands.MapObjectsCommands; import com.mobidevelop.utils.commands.CommandManager; import com.mobidevelop.utils.events.Event; import com.mobidevelop.utils.events.EventDispatcher; @@ -87,8 +92,20 @@ public static class MapModel extends Model { public MapModel(Map map) { this.map = map; + commands = new CommandManager( this ); + layersModel = new MapLayersModel( this ); + objectsModel = new MapObjectsModel( this ); } + + @Override + public void addEventListener( EventListener listener ) { + super.addEventListener( listener ); + + layersModel.addEventListener( listener ); + objectsModel.addEventListener( listener ); + } + public String getFile() { return file; } @@ -103,43 +120,174 @@ public boolean save() { public boolean saveAs(String file) { return false; } - - } + + public void addLayer() { + layersModel.addLayer(); + } + public void removeLayer( MapLayer layer ) { + // deleting current layer, set layer to next nearest + if ( layersModel.getCurrentLayer() == layer ) { + int index = map.getLayers().getIndex( layer ) - 1; + if ( index < 0 ) + index = 1; + MapLayer nextLayer = map.getLayers().getLayer( index ); + layersModel.setCurrentLayer( nextLayer ); + } + layersModel.removeLayer( layer ); + } + + public MapLayer currentLayer() { + return layersModel.getCurrentLayer(); + } + + public int numLayers() { + + return map.getLayers().getCount(); + } + + public void addImageBrush( MapLayer layer, MapObject object, FileHandle file ) { + + objectsModel.add( layer, object, file ); + } + + public void setCurrentLayer( MapLayer layer ) { + + layersModel.setCurrentLayer( layer ); + } + + public void showLayer( MapLayer layer ) { + + layersModel.hideLayer( layer ); + } + + public void hideLayer( MapLayer layer ) { + + layersModel.showLayer( layer ); + } + } + public static class MapLayersModel extends Model { private MapModel model; + private int currentLayer = 0; + private int lastCreatedLayer = 0; - public MapLayersModel() { - + public MapLayersModel(MapModel mapModel) { + + setMapModel( mapModel ); } + public void setCurrentLayer( MapLayer layer ) { + + currentLayer = model.map.getLayers().getIndex( layer ); + dispatchEvent( new LayerSelectedEvent( layer ) ); + } + + public MapLayer getCurrentLayer() { + + return this.model.map.getLayers().getLayer( currentLayer ); + } + public void setMapModel(MapModel model) { this.model = model; } - - public void addLayer(MapLayer layer) { - this.model.map.getLayers().addLayer(layer); - this.dispatchEvent(new LayerAddedEvent()); + + public void addLayer() { + model.commands.execute( MapLayersCommands.add( model.map, "Layer " + ++lastCreatedLayer ) ); } public void removeLayer(MapLayer layer) { - this.model.map.getLayers().removeLayer(layer); - this.dispatchEvent(new LayerRemovedEvent()); + model.commands.execute( MapLayersCommands.remove( model.map, layer ) ); } - - public static class LayerAddedEvent extends Event { - + + public void hideLayer( MapLayer layer ) { + model.commands.execute( MapLayerCommands.hide( layer ) ); } - public static class LayerRemovedEvent extends Event { - + + public void showLayer( MapLayer layer ) { + model.commands.execute( MapLayerCommands.show( layer ) ); + } + + public static class LayerEvent extends Event { + public MapLayer layer; + public LayerEvent( MapLayer layer ) { + this.layer = layer; + } + } + public static class LayerAddedEvent extends LayerEvent { + public LayerAddedEvent( MapLayer layer ) { super( layer ); } + } + public static class LayerRemovedEvent extends LayerEvent { + public LayerRemovedEvent( MapLayer layer ) { super( layer ); } + } + public static class LayerSelectedEvent extends LayerEvent { + public LayerSelectedEvent( MapLayer layer ) { super( layer ); } + } + public static class LayerSwappedEvent extends LayerEvent { + public MapLayer layer2; + public LayerSwappedEvent( MapLayer layer, MapLayer layer2 ) { + super( layer ); + this.layer2 = layer2; + } + } + public static class LayerHiddenEvent extends LayerEvent { + public LayerHiddenEvent( MapLayer layer ) { super( layer ); } + } + public static class LayerShownEvent extends LayerEvent { + public LayerShownEvent( MapLayer layer ) { super( layer ); } } } - + public static class MapObjectsModel extends Model { - private MapLayer model; - + private MapModel model; + + public MapObjectsModel( MapModel mapModel ) { + + setMapModel( mapModel ); + } + + public void setMapModel(MapModel model) { + this.model = model; + } + + public void add( MapLayer layer, MapObject mapObject, FileHandle file ) { + + model.commands.execute( MapObjectsCommands.add( layer, mapObject, file ) ); + } + + public static class ObjectAddedEvent extends Event { + + public MapLayer layer; + public MapObject object; + public FileHandle file; + public ObjectAddedEvent( MapLayer layer, MapObject object, FileHandle file ) { + + this.layer = layer; + this.object = object; + this.file = file; + } + } + public static class ObjectRemovedEvent extends Event { + + public MapLayer layer; + public MapObject object; + public ObjectRemovedEvent( MapLayer layer, MapObject object ) { + + this.layer = layer; + this.object = object; + } + } + public static class ObjectSwappedEvent extends Event { + + public MapObject object1; + public MapObject object2; + public ObjectSwappedEvent( MapObject object1, MapObject object2 ) { + + this.object1 = object1; + this.object2 = object2; + } + } } - } diff --git a/maps-editor/src/com/mobidevelop/maps/editor/ui/FileDialog.java b/maps-editor/src/com/mobidevelop/maps/editor/ui/FileDialog.java new file mode 100644 index 0000000..817efb9 --- /dev/null +++ b/maps-editor/src/com/mobidevelop/maps/editor/ui/FileDialog.java @@ -0,0 +1,108 @@ +package com.mobidevelop.maps.editor.ui; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.ui.List; +import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane; +import com.badlogic.gdx.scenes.scene2d.ui.Skin; +import com.badlogic.gdx.scenes.scene2d.ui.Table; +import com.badlogic.gdx.scenes.scene2d.ui.TextButton; +import com.badlogic.gdx.scenes.scene2d.ui.TextField; +import com.badlogic.gdx.scenes.scene2d.ui.Window; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; + + +public class FileDialog extends Window { + + List fileList; + TextField fileName; + private String lastFolder; + + public FileDialog( String title, FileHandle initialFolder, Skin skin ) { + + super( title, skin ); + + setModal( true ); + setFillParent( true ); + + lastFolder = initialFolder.path(); + fileList = new List( initialFolder.list(), skin ); + fileList.addListener( new ClickListener() { + @Override + public void clicked( InputEvent event, float x, float y ) { + + String path = lastFolder + "/" + Gdx.files.external( fileList.getSelection() ).name(); + FileHandle file = Gdx.files.external( path ); + if ( file.isDirectory() ) { + lastFolder = path; + loadFileList( file.list() ); + } else { + fileName.setText( file.name() ); + } + } + } ); + final ScrollPane listScroll = new ScrollPane( fileList ); + + final TextButton upButton = new TextButton( "Up", skin ); + upButton.addListener( new ClickListener() { + @Override + public void clicked( InputEvent event, float x, float y ) { + lastFolder = Gdx.files.external( lastFolder ).parent().name(); + loadFileList( Gdx.files.external( lastFolder ).list() ); + } + }); + + final TextButton okButton = new TextButton( "OK", skin ); + okButton.addListener( new ClickListener() { + @Override + public void clicked( InputEvent event, float x, float y ) { + + String path = lastFolder + "/" + fileList.getSelection(); + FileHandle file = Gdx.files.external( path ); +// if ( file.isDirectory() ) { +// loadFileList( file.list() ); +// } else { + fire( UIEvents.selectImagePath( file ) ); + remove(); +// } + } + }); + final TextButton cancelButton = new TextButton( "Cancel", skin ); + cancelButton.addListener( new ClickListener() { + public void clicked( InputEvent event, float x, float y ) { + remove(); + } + } ); + + fileName = new TextField( "", skin ); + + Table table = new Table( skin ); + table.defaults().fill().padTop( 10 ).padBottom( 10 ); + table.row(); + table.add( upButton ).colspan( 2 ); + table.row(); + table.add( listScroll ).expand().colspan( 2 ); + table.row(); + table.add( fileName ); + table.row(); + table.add( okButton ); + table.add( cancelButton ); + + add( table ); + } + + public void editFileName( boolean enable ) { + + fileName.setDisabled( !enable ); + } + + private void loadFileList( FileHandle[] list ) { + + String[] items = new String[list.length]; + for ( int i = 0; i < items.length; i++ ) { + items[i] = list[i].name(); + } + fileList.setItems( items ); + } +} diff --git a/maps-editor/src/com/mobidevelop/maps/editor/ui/ImagePicker.java b/maps-editor/src/com/mobidevelop/maps/editor/ui/ImagePicker.java new file mode 100644 index 0000000..2b8434c --- /dev/null +++ b/maps-editor/src/com/mobidevelop/maps/editor/ui/ImagePicker.java @@ -0,0 +1,115 @@ +package com.mobidevelop.maps.editor.ui; + +import java.util.HashMap; + +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Pixmap.Format; +import com.badlogic.gdx.graphics.Texture.TextureFilter; +import com.badlogic.gdx.graphics.g2d.PixmapPacker; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.ui.Image; +import com.badlogic.gdx.scenes.scene2d.ui.Label; +import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane; +import com.badlogic.gdx.scenes.scene2d.ui.Skin; +import com.badlogic.gdx.scenes.scene2d.ui.Table; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Disposable; + + +public class ImagePicker extends Table implements Disposable { + + private PixmapPacker thumbPacker = null; + private TextureAtlas thumbImageAtlas; + private HashMap projectImages; + + private Table thumbTable; + + public ImagePicker( final Skin skin ) { + + thumbImageAtlas = new TextureAtlas(); + projectImages = new HashMap(); + + // thumbnails sit in a table within a scroll pane + thumbTable = new Table(); + thumbTable.row(); + thumbTable.add( new Label( "No images loaded", skin ) ); + final ScrollPane thumbScroll = new ScrollPane( thumbTable ); + + // add click listener to send event for new image to stage + thumbScroll.addListener( new ClickListener() { + @Override + public void clicked( InputEvent event, float x, float y ) { + + Actor actor = thumbScroll.hit( x, y, false ); + if ( actor != null ) { + FileHandle file = projectImages.get( actor.getName() ); + if ( file != null ) + fire( UIEvents.addImageEvent( file ) ); + } + super.clicked( event, x, y ); + } + } ); + + add( thumbScroll ); + } + + /** + * creates a 64x64 thumbnail from the given file (should be an image file) + * @param img + */ + private void packImageThumb( FileHandle img ) { + + projectImages.put( img.name(), img ); + Pixmap fullImage = new Pixmap( img ); + Pixmap thumb = new Pixmap( 64, 64, Format.RGB565 ); + thumb.drawPixmap( fullImage, 0, 0, fullImage.getWidth(), fullImage.getHeight(), 0, 0, thumb.getWidth(), thumb.getHeight() ); + thumbPacker.pack( img.name(), thumb ); + } + + /** + * loads images from the project image folder into thumbnails on the image window + * @param file the folder to load images from + */ + public void loadProjectImages( FileHandle folder ) { + + // reset thumbs + if ( thumbPacker != null ) + thumbPacker.dispose(); + thumbPacker = new PixmapPacker( 1024, 1024, Format.RGBA8888, 2, false ); + projectImages.clear(); + + // get image thumbs and pack into atlas + FileHandle[] list = folder.list(); + for ( int i = 0; i < list.length; i++ ) { + FileHandle img = list[i]; + String ext = img.extension().toLowerCase(); + if ( ext.contentEquals( "png" ) || ext.contentEquals( "jpg" ) ) { + packImageThumb( img ); + } + } + thumbPacker.updateTextureAtlas( thumbImageAtlas, TextureFilter.Linear, TextureFilter.Linear, true ); + + // load thumbs into image listbox + Array regions = thumbImageAtlas.getRegions(); + thumbTable.clear(); + for ( int i = 0; i < regions.size; i++ ) { + AtlasRegion region = regions.get( i ); + Image img = new Image( region ); + img.setName( region.name ); + thumbTable.row(); + thumbTable.add( img ); + } + } + + @Override + public void dispose() { + + if ( thumbPacker != null ) + thumbPacker.dispose(); + } +} diff --git a/maps-editor/src/com/mobidevelop/maps/editor/ui/LayerList.java b/maps-editor/src/com/mobidevelop/maps/editor/ui/LayerList.java new file mode 100644 index 0000000..5a028ce --- /dev/null +++ b/maps-editor/src/com/mobidevelop/maps/editor/ui/LayerList.java @@ -0,0 +1,181 @@ +package com.mobidevelop.maps.editor.ui; + +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.Touchable; +import com.badlogic.gdx.scenes.scene2d.ui.Button; +import com.badlogic.gdx.scenes.scene2d.ui.CheckBox; +import com.badlogic.gdx.scenes.scene2d.ui.Label; +import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle; +import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane; +import com.badlogic.gdx.scenes.scene2d.ui.Skin; +import com.badlogic.gdx.scenes.scene2d.ui.Table; +import com.badlogic.gdx.scenes.scene2d.ui.TextButton; +import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.utils.SnapshotArray; +import com.mobidevelop.maps.MapLayer; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerAddedEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerRemovedEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerSelectedEvent; +import com.mobidevelop.maps.editor.models.MapModels.MapLayersModel.LayerSwappedEvent; +import com.mobidevelop.utils.events.Event; +import com.mobidevelop.utils.events.EventDispatcher; +import com.mobidevelop.utils.events.EventListener; + + +public class LayerList extends Table implements EventListener { + + private Skin skin; + private VerticalGroup layers; + private Label lastSelected; + + public LayerList( final Skin skin ) { + + this.skin = skin; + + // rich list box for the layers, just a vertical group of LayerActors in a ScrollPane + layers = new VerticalGroup(); + layers.addListener( new ClickListener() { + @Override + public void clicked( InputEvent event, float x, float y ) { + Actor actor = layers.hit( x, y, true ); + if ( actor instanceof Label ) { + // hacky way to get the layer + Label lbl = (Label) actor; + fire( UIEvents.selectLayer( ((LayerListActor)lbl.getParent()).getLayer() ) ); + } + } + } ); + final ScrollPane layerScroll = new ScrollPane( layers ); + layerScroll.setOverscroll( false, true ); + + // controls for add/removing layers + final TextButton addLayerButton = new TextButton( "New Layer", skin ); + addLayerButton.addListener( new ClickListener() { + @Override + public void clicked( InputEvent event, float x, float y ) { + fire( UIEvents.addLayerEvent() ); + } + }); + final TextButton removeLayerButton = new TextButton( "Remove Layer", skin ); + removeLayerButton.addListener( new ClickListener() { + @Override + public void clicked( InputEvent event, float x, float y ) { + fire( UIEvents.removeLayerEvent() ); + } + }); + + defaults().pad( 10 ); + row(); + add( layerScroll ).colspan( 2 ); + row(); + add( addLayerButton ); + add( removeLayerButton ); + } + + @Override + public void onEvent( EventDispatcher dispatcher, Event event ) { + + // model added layer + if ( event instanceof LayerAddedEvent ) { + MapLayer layer = ((LayerAddedEvent)event).layer; + LayerListActor actor = new LayerListActor( layer, skin ); + layers.addActorAt( layer.getMap().getLayers().getIndex( layer ), actor ); + return; + } + + // model removed layer + if ( event instanceof LayerRemovedEvent ) { + Actor actor = getLayerActor( ((LayerRemovedEvent)event).layer ); + if ( actor != null ) + actor.remove(); + return; + } + + // model changed selected layer + if ( event instanceof LayerSelectedEvent ) { + selectLayer( ((LayerSelectedEvent)event).layer ); + } + + // model swapped layers + if ( event instanceof LayerSwappedEvent ) { + LayerSwappedEvent evt = (LayerSwappedEvent)event; + Actor actor1 = getLayerActor( evt.layer ); + Actor actor2 = getLayerActor( evt.layer2 ); + if ( actor1 != null && actor2 != null ) + layers.swapActor( actor1, actor2 ); + return; + } + } + + public LayerListActor getLayerActor( MapLayer layer ) { + + SnapshotArray ls = layers.getChildren(); + for ( int i = 0; i < ls.size; i++ ) { + LayerListActor l = (LayerListActor)ls.get( i ); + if ( layer == l.getLayer() ) + return l; + } + return null; + } + + public void selectLayer( MapLayer layer ) { + + if ( lastSelected != null ) { + lastSelected.setStyle( skin.get( "default", LabelStyle.class ) ); + } + lastSelected = getLayerActor( layer ).getLabel(); + lastSelected.setStyle( skin.get( "selected", LabelStyle.class ) ); + } + + // LayerListActor will represent a layer in the listbox, with controls to handle locking/visibility, etc. + public class LayerListActor extends Table { + + private MapLayer layer; + private Label label; + + public LayerListActor( MapLayer layer, Skin skin ) { + + this.layer = layer; + + setTouchable( Touchable.childrenOnly ); + setName( layer.getName() ); + label = new Label( getName(), skin ); + label.setTouchable( Touchable.enabled ); + + final CheckBox showHideButton = new CheckBox( "", skin ); + showHideButton.addListener( new ClickShowHideListener( showHideButton, layer ) ); + showHideButton.setChecked( true ); + + row(); + add( showHideButton ); + add( label ); + } + + public Label getLabel() { + return label; + } + + public MapLayer getLayer() { + return layer; + } + } + + public class ClickShowHideListener extends ClickListener { + private Button button; + private MapLayer layer; + public ClickShowHideListener( Button button, MapLayer layer ) { + this.button = button; + this.layer = layer; + } + @Override + public void clicked( InputEvent event, float x, float y ) { + if ( button.isChecked() ) + fire( UIEvents.hideLayer( layer ) ); + else + fire( UIEvents.showLayer( layer ) ); + super.clicked( event, x, y ); + } + } +} diff --git a/maps-editor/src/com/mobidevelop/maps/editor/ui/SlideWindow.java b/maps-editor/src/com/mobidevelop/maps/editor/ui/SlideWindow.java new file mode 100644 index 0000000..3ab1684 --- /dev/null +++ b/maps-editor/src/com/mobidevelop/maps/editor/ui/SlideWindow.java @@ -0,0 +1,148 @@ +package com.mobidevelop.maps.editor.ui; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.math.Interpolation; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.Touchable; +import com.badlogic.gdx.scenes.scene2d.actions.Actions; +import com.badlogic.gdx.scenes.scene2d.ui.Skin; +import com.badlogic.gdx.scenes.scene2d.ui.TextButton; +import com.badlogic.gdx.scenes.scene2d.ui.Window; +import com.badlogic.gdx.scenes.scene2d.utils.Align; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; + + +public class SlideWindow extends Window { + + private Vector2 startPos = new Vector2(); + private Vector2 goalPos = new Vector2(); + + /** + * creates a window that slides in from screen edge determined by origin (using {@link com.badlogic.gdx.scenes.scene2d.utils.Align Align}) + * @param skin skin file to use + * @param origin which edge of the screen to slide from (see {@link com.badlogic.gdx.scenes.scene2d.utils.Align Align}) + * @param margin amount of empty space to leave around window + */ + public SlideWindow( Skin skin, int origin, float margin ) { + + this( skin, origin, margin, 0 ); + } + + /** + * creates a window that slides in from screen edge determined by origin (using {@link com.badlogic.gdx.scenes.scene2d.utils.Align Align}) + * @param skin skin file to use + * @param origin which edge of the screen to slide from (see {@link com.badlogic.gdx.scenes.scene2d.utils.Align Align}) + * @param margin amount of empty space to leave around window + * @param lip size of window along sliding axis (0 = fullscreen) + */ + public SlideWindow( Skin skin, int origin, float margin, float lip ) { + + super( "", skin ); + + float w = Gdx.graphics.getWidth(); + float h = Gdx.graphics.getHeight(); + float ww = w - 2*margin; + float wh = h - 2*margin; + + // setup window with desired dimensions to slide in from edge of screen determined by origin + if ( ( origin & Align.left ) != 0 ) { + + ww = lip > 0 ? lip : ww; + setupWindow( skin, ww, wh, -( ww + 2*margin ), margin, margin, margin ); + + } else if ( ( origin & Align.right ) != 0 ) { + + ww = lip > 0 ? lip : ww; + setupWindow( skin, ww, wh, w + 2*margin, margin, w - ( ww + margin ), margin ); + + } else if ( ( origin & Align.top ) != 0 ) { + + wh = lip > 0 ? lip : wh; + setupWindow( skin, ww, wh, margin, h + 2*margin, margin, h - ( wh + margin ) ); + + } else if ( ( origin & Align.bottom ) != 0 ) { + + wh = lip > 0 ? lip : wh; + setupWindow( skin, ww, wh, margin, -( wh + 2*margin ), margin, margin ); + + // default center + } else { + + setupWindow( skin, ww, wh, margin, margin, margin, margin ); + } + } + + /** + * creates a slide window with all properties explicitly defined + * @param skin + * @param windowWidth + * @param windowHeight + * @param startPosX + * @param startPosY + * @param goalPosX + * @param goalPosY + */ + public SlideWindow( Skin skin, float windowWidth, float windowHeight, float startPosX, float startPosY, float goalPosX, float goalPosY ) { + + super( "", skin ); + + setupWindow( skin, windowWidth, windowHeight, startPosX, startPosY, goalPosX, goalPosY ); + } + + private void setupWindow( Skin skin, float width, float height, float startPosX, float startPosY, float goalPosX, float goalPosY ) { + + startPos.set( startPosX, startPosY ); + goalPos.set( goalPosX, goalPosY ); + +// // remove listener which pushes window to front when clicked +// removeCaptureListener( getCaptureListeners().get( 0 ) ); + + // kill click events on the window + addListener( new ClickListener() { + @Override + public boolean touchDown( InputEvent event, float x, float y, int pointer, int button ) { + super.touchDown( event, x, y, pointer, button ); + return true; + } + } ); + setTouchable( Touchable.enabled ); + setBackground( skin.getDrawable( "default-round" ) ); + setKeepWithinStage( false ); + + // add a close button + TextButton dismisser = new TextButton( "X", skin ); + dismisser.addListener( new ClickListener() { + @Override + public void clicked( InputEvent event, float x, float y ) { + hideWindow(); + } + }); + row(); + add( dismisser ).align( Align.right ); + + setSize( width, height ); + setPosition( startPos.x, startPos.y ); + } + + /** + * show the window with a slide and fade-in + */ + public void showWindow() { + + clearActions(); + setPosition( startPos.x, startPos.y ); + setColor( 1, 1, 1, 0 ); + addAction( Actions.parallel( Actions.moveTo( goalPos.x, goalPos.y, 0.3f, Interpolation.swingOut ), Actions.fadeIn( 0.3f ) ) ); + } + + /** + * hide the window with a slide and fade-out + */ + public void hideWindow() { + + clearActions(); + setPosition( goalPos.x, goalPos.y ); + addAction( Actions.parallel( Actions.moveTo( startPos.x, startPos.y, 0.3f ), Actions.fadeOut( 0.3f, Interpolation.exp10Out ) ) ); + } +} diff --git a/maps-editor/src/com/mobidevelop/maps/editor/ui/UIEvents.java b/maps-editor/src/com/mobidevelop/maps/editor/ui/UIEvents.java new file mode 100644 index 0000000..73d94d6 --- /dev/null +++ b/maps-editor/src/com/mobidevelop/maps/editor/ui/UIEvents.java @@ -0,0 +1,66 @@ +package com.mobidevelop.maps.editor.ui; + +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.scenes.scene2d.Event; +import com.mobidevelop.maps.MapLayer; +import com.mobidevelop.maps.MapObject; + + +public class UIEvents { + + public static final int EV_ADD_LAYER = 1; + public static final int EV_REMOVE_LAYER = 2; + public static final int EV_SELECT_LAYER = 3; + public static final int EV_HIDE_LAYER = 4; + public static final int EV_SHOW_LAYER = 5; + public static final int EV_ADD_IMAGE = 6; + public static final int EV_SELECT_MAP_OBJECT = 7; + public static final int EV_SELECT_IMAGE_PATH = 8; + + /** + * UIEvents are events the scene2d.ui uses to interact with the model. + * They are sent by different ui components and handled by the main uiStage, where the model interaction occurs. + * The model has it's own events which each ui component responds to as needed. + * @author Josh + * + */ + public static class UIEvent extends Event { + public int type; + public UIEvent( int type ) { + this.type = type; + } + } + + public static class LayerEvent extends UIEvent { + public MapLayer layer; + public LayerEvent( int type, MapLayer layer ) { + super( type ); + this.layer = layer; + } + } + + public static class MapObjectEvent extends UIEvent { + public MapObject object; + public MapObjectEvent( int type, MapObject object ) { + super( type ); + this.object = object; + } + } + + public static class FileEvent extends UIEvent { + public FileHandle file; + public FileEvent( int type, FileHandle file ) { + super( type ); + this.file = file; + } + } + + public static UIEvent addLayerEvent() { return new UIEvent( EV_ADD_LAYER ); } + public static UIEvent removeLayerEvent() { return new UIEvent( EV_REMOVE_LAYER ); } + public static UIEvent selectLayer( MapLayer layer ) { return new LayerEvent( EV_SELECT_LAYER, layer ); } + public static UIEvent hideLayer( MapLayer layer ) { return new LayerEvent( EV_HIDE_LAYER, layer ); } + public static UIEvent showLayer( MapLayer layer ) { return new LayerEvent( EV_SHOW_LAYER, layer ); } + public static UIEvent addImageEvent( FileHandle file ) { return new FileEvent( EV_ADD_IMAGE, file ); } + public static UIEvent selectObject( MapObject object ) { return new MapObjectEvent( EV_SELECT_MAP_OBJECT, object ); } + public static UIEvent selectImagePath( FileHandle file ) { return new FileEvent( EV_SELECT_IMAGE_PATH, file ); } +} diff --git a/maps-editor/src/com/mobidevelop/utils/commands/Command.java b/maps-editor/src/com/mobidevelop/utils/commands/Command.java index ccba5eb..12d11f6 100644 --- a/maps-editor/src/com/mobidevelop/utils/commands/Command.java +++ b/maps-editor/src/com/mobidevelop/utils/commands/Command.java @@ -16,6 +16,8 @@ package com.mobidevelop.utils.commands; +import com.mobidevelop.utils.events.Event; + /** * Represents an action that can be executed and reversed. * @@ -23,8 +25,8 @@ */ public interface Command { - public void execute(); + public Event execute(); - public void reverse(); + public Event reverse(); } diff --git a/maps-editor/src/com/mobidevelop/utils/commands/CommandManager.java b/maps-editor/src/com/mobidevelop/utils/commands/CommandManager.java index 5c14482..d8b03d8 100644 --- a/maps-editor/src/com/mobidevelop/utils/commands/CommandManager.java +++ b/maps-editor/src/com/mobidevelop/utils/commands/CommandManager.java @@ -18,6 +18,9 @@ import java.util.*; +import com.mobidevelop.maps.editor.models.MapModels.MapModel; +import com.mobidevelop.utils.events.Event; + /** * The {@code CommandManager} class manages the execution and reversal of {@link Command Commands}. @@ -28,8 +31,11 @@ public class CommandManager { private Deque undoStack; private Deque redoStack; - - public CommandManager() { + + private MapModel model; + + public CommandManager( MapModel model ) { + this.model = model; undoStack = new ArrayDeque(); redoStack = new ArrayDeque(); } @@ -40,7 +46,9 @@ public CommandManager() { * @param command The {@link Command} to execute. */ public void execute(Command command) { - command.execute(); + Event event = command.execute(); + if ( event != null ) + model.dispatchEvent( event ); undoStack.push(command); redoStack.clear(); } @@ -55,7 +63,9 @@ public void undo() { throw new RuntimeException("No commands to undo."); } Command command = undoStack.pop(); - command.reverse(); + Event event = command.reverse(); + if ( event != null ) + model.dispatchEvent( event ); redoStack.push(command); } @@ -70,7 +80,9 @@ public void undo(int count) { } while (!undoStack.isEmpty() && count > 0) { Command command = undoStack.pop(); - command.reverse(); + Event event = command.reverse(); + if ( event != null ) + model.dispatchEvent( event ); redoStack.push(command); count--; } @@ -103,7 +115,9 @@ public void redo() { throw new RuntimeException("No commands to redo."); } Command command = redoStack.pop(); - command.execute(); + Event event = command.execute(); + if ( event != null ) + model.dispatchEvent( event ); undoStack.push(command); } @@ -113,7 +127,9 @@ public void redo(int count) { } while (!undoStack.isEmpty() && count > 0) { Command command = redoStack.pop(); - command.execute(); + Event event = command.execute(); + if ( event != null ) + model.dispatchEvent( event ); undoStack.push(command); count--; } diff --git a/maps/src/com/mobidevelop/maps/Map.java b/maps/src/com/mobidevelop/maps/Map.java index 0e8d1b1..ea4b95d 100644 --- a/maps/src/com/mobidevelop/maps/Map.java +++ b/maps/src/com/mobidevelop/maps/Map.java @@ -46,4 +46,5 @@ public interface Map extends Disposable { public abstract MapResources getResources(); + public abstract MapLayer createLayer( String name ); }