From c764916cef363e15865870ad10bf95a1aed42c7a Mon Sep 17 00:00:00 2001 From: Int-0 Date: Wed, 29 Nov 2017 21:09:19 +0100 Subject: [PATCH 1/4] Adding more custom dialogs: Error dialog --- artwork/error.svg | 102 +++++++++++++++++++++++ examples/dialogs_oeverview_app.py | 49 +++++++++++ remi/dialogs.py | 132 ++++++++++++++++++++++++++++++ remi/res/error.png | Bin 0 -> 2253 bytes 4 files changed, 283 insertions(+) create mode 100644 artwork/error.svg create mode 100644 examples/dialogs_oeverview_app.py create mode 100644 remi/dialogs.py create mode 100644 remi/res/error.png diff --git a/artwork/error.svg b/artwork/error.svg new file mode 100644 index 00000000..e731dae0 --- /dev/null +++ b/artwork/error.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/examples/dialogs_oeverview_app.py b/examples/dialogs_oeverview_app.py new file mode 100644 index 00000000..616c977b --- /dev/null +++ b/examples/dialogs_oeverview_app.py @@ -0,0 +1,49 @@ +""" + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + +import remi.gui as gui +import remi.dialogs as dialogs +from remi import start, App + + +class MyApp(App): + def __init__(self, *args): + super(MyApp, self).__init__(*args) + + def main(self): + # the margin 0px auto centers the main container + verticalContainer = gui.Widget( + width=540, margin='0px auto', + style={'display': 'block', 'overflow': 'hidden'}) + + error_bt = gui.Button('Show error dialog', + width=200, height=30, margin='10px') + + # setting the listener for the onclick event of the button + error_bt.set_on_click_listener(self.show_error_dialog) + + verticalContainer.append(error_bt) + + # returning the root widget + return verticalContainer + + def show_error_dialog(self, widget): + dialogs.Error('Some error message', width=300).show(self) + +if __name__ == "__main__": + # starts the webserver + # optional parameters + # start(MyApp,address='127.0.0.1', port=8081, multiple_instance=False,enable_file_cache=True, update_interval=0.1, start_browser=True) + + start(MyApp, debug=True, address='0.0.0.0', start_browser=True) diff --git a/remi/dialogs.py b/remi/dialogs.py new file mode 100644 index 00000000..b04170fd --- /dev/null +++ b/remi/dialogs.py @@ -0,0 +1,132 @@ +""" + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + +import sys + +from .server import runtimeInstances, update_event +from .gui import decorate_set_on_listener, decorate_constructor_parameter_types +from .gui import Widget, Label, Image, Button + +class _DialogBase(Widget): + """Base information dialog. Information dialog can be used to show some + info to the user. The dialog can be "information", "alert" and "error". + In all cases the dialog is exactly the same but the icon changes. + + The Ok button emits the 'confirm_dialog' event. Register the listener to + it with set_on_confirm_dialog_listener. + """ + + EVENT_ONCONFIRM = 'confirm_dialog' + + @decorate_constructor_parameter_types([str, Widget]) + def __init__(self, title='', content=None, **kwargs): + """ + Args: + title (str): The title of the dialog. + message (str): The message description. + kwargs: See Widget.__init__() + """ + super(_DialogBase, self).__init__(**kwargs) + self.set_layout_orientation(Widget.LAYOUT_VERTICAL) + self.style['display'] = 'block' + self.style['overflow'] = 'auto' + self.style['margin'] = '0px auto' + + if len(title) > 0: + t = Label(title) + t.add_class('DialogTitle') + self.append(t) + + if content is not None: + self.append(content) + + self.container = Widget() + self.container.style['display'] = 'block' + self.container.style['overflow'] = 'auto' + self.container.style['margin'] = '5px' + self.container.set_layout_orientation(Widget.LAYOUT_VERTICAL) + self.conf = Button('Ok') + self.conf.set_size(100, 30) + self.conf.style['margin'] = '3px' + hlay = Widget(height=35) + hlay.style['display'] = 'block' + hlay.style['overflow'] = 'visible' + hlay.append(self.conf) + self.conf.style['float'] = 'right' + + self.append(self.container) + self.append(hlay) + + self.conf.attributes[self.EVENT_ONCLICK] = "sendCallback('%s','%s');" % (self.identifier, self.EVENT_ONCONFIRM) + + self.inputs = {} + + self._base_app_instance = None + self._old_root_widget = None + + def confirm_dialog(self): + """Event generated by the OK button click. + """ + self.hide() + return self.eventManager.propagate(self.EVENT_ONCONFIRM, ()) + + @decorate_set_on_listener("confirm_dialog", "(self,emitter)") + def set_on_confirm_dialog_listener(self, callback, *userdata): + """Registers the listener for the GenericDialog.confirm_dialog event. + + Note: The prototype of the listener have to be like my_on_confirm_dialog(self, widget). + + Args: + callback (function): Callback function pointer. + """ + self.eventManager.register_listener(self.EVENT_ONCONFIRM, callback, *userdata) + + def show(self, base_app_instance): + self._base_app_instance = base_app_instance + self._old_root_widget = self._base_app_instance.root + self._base_app_instance.set_root_widget(self) + + def hide(self): + self._base_app_instance.set_root_widget(self._old_root_widget) + + +class Error(_DialogBase): + """Show a error dialog with a little message and button to accept. + + The Ok button emits the 'confirm_dialog' event. Register the listener to + it with set_on_confirm_dialog_listener. + """ + @decorate_constructor_parameter_types([str, Widget]) + def __init__(self, message='', **kwargs): + if len(message) == 0: + message = 'Unknown error' + super(Error, self).__init__( + title='Error', + content=_make_content_(message, '/res/error.png'), + **kwargs) + +def _make_content_(message, icon_name): + container = Widget() + container.style['display'] = 'block' + container.style['overflow'] = 'auto' + container.style['margin'] = '5px' + container.set_layout_orientation(Widget.LAYOUT_HORIZONTAL) + icon = Image(icon_name) + icon.style['margin'] = '5px' + label = Label(message) + label.style['margin'] = '5px' + container.append(icon) + container.append(label) + return container + diff --git a/remi/res/error.png b/remi/res/error.png new file mode 100644 index 0000000000000000000000000000000000000000..249489d7fdf617856f1e3935308002200d691fd2 GIT binary patch literal 2253 zcmV;;2r~DHP)R=zRF$FvY;G6~bEieEK0}fO;7vKTBfDcdr`oPiz zvpV2uCs=?wH&na|5C3PtYA6%non$aB0wo{%bzn!;kfvCWvIZ#tq6~&>F#8%Tbb+A2 z_MLEILV$-s?}bAOY+3-i`G5!#;R=Nu3W0QMkRwq95&%75^g_x0#+=&`$|A4Qi4?p7sz8YYISm0l(H))BsIDB9PPy1s9-HhDJTC-KI`3 z%)}Ux`;!Tld=m^=;mvN;B@C4_-A1qn}@12gwbRiPZ z3y1Wu^aU`aM>eV!8g)gJUy~1YXg+=i?BxS3xK7vuw0lHLho56kyzE=u2&Oq}hz;|J? z0%sS1u^>7efWJep*B#Hz&9RH3_>|N6Km!aYa_JqwaP`!wo0~H-D!XL)lP|j6U0T$2 znnXazgkc+)ZBVufj(ni@4=%9}s#3r(KQVGc?%!|vlE>4Fn$-ybi6H8LPIzG@)O`q&2(SVwQsKF!nnN_W3x$QeR9}zY zWE%N zzsb&Ck+o&ZvXyVXnG)$Qh$7Ft_@ep!y1GS^ZMJih)PykrM!={8g9Mfbuw*0rT>KU+ zlws#G%`4K3fGh~y=zbhWkdpT0iwmM@R$lUGzEb`K1s z8|Tf-Dc!d(G1hmT*}GTz{OHl&Z|mwhPVlW_3RMyaO)x`-UU4<-o&mG-#wi@0bh+qk zY2n6$2V+GtA-|*~=0wlE@y1Z+kt1iec6XnMfExwb1v!Ht2m;JakF)GrVzXg5dX%%f zcgN!%4p&*}=&r53xTUA(6p)HqcA0@B4a$I;)gmCK!mt2y1Q{}8Zk8yvbOMm|ufOiR zfB5hx8+&@r0%;+DDH>!Tf<_HA!2)gtvIGT^q9sQ>koD!|WWJ2G|6X`{9yU3V9w_{LB~!HNZuy72npaV=im`Yp-#$zCPw*KBK5e zxYOUC+|kx%&UZT9vEYEjMo9C(32`NO-LP#A(O-2C-=6!Zq5`G1mV_%;#y-rKEfa1_ zlDWO5#ry@Q(@!vpBl~a=lCOYOhCNh*Py_d_f}%u`1j>Qw(-p7FrNpvx<>ktGt97K$ zd8<`9S6&{|-|KQI`=?L$4??d3U7C6nxTiqhC2-Y1Z)KofJqSPjCAj4nkarRilw*q* zD;}qF)L;#J_9*Al(?|6=pO&WldDpH{{yw)``BhPoa#OE&D=@6Ukfvb;oHBThfx8;2 zM#5eLxz*tPECygK$c8<8lnYVd0{W}f8U`7FyQSC1?9&cPHF$ppQ^NK}HPjvhw=Yh} z@!%pML9MR;n3Kc2!9kYTY-4x^?P6LQS5s42 z+tb67-l!J{0DKHw1Dgl(cR~LshlB9lYLGj}iHbB%`$YPDt}d-y4c{Jfw$)I32t0ig zfS9QIKzSEDZ^Jv0Jfm(F46wrw=TCxXbc`Rr^#dotp3iP@E zC%8Mn2jKNXg9p~`(tfOm+NjGTfYabz1Hbh`PCGp72Xa6+D%=w^AMg+0g1W@D4`Ka} z)KYAejawkR1>Z@4LrIYMJQ$ver&A15fX{%xfyV(31zvsyem7R1xW;-LG8FYXVoM6h zbHVU5YP}whNg7x)cN6I!iG5Yl134vK*VBFvQ`TTy>kpaX}X0yQgeNe2ym@XyK# b{2SuGs$ZM+xAlO|00000NkvXXu0mjf*B%x! literal 0 HcmV?d00001 From 175fe43583808f40ae0bc1326fa5d33bc3a37834 Mon Sep 17 00:00:00 2001 From: Int-0 Date: Thu, 30 Nov 2017 21:04:26 +0100 Subject: [PATCH 2/4] Adding information dialogs --- artwork/info.svg | 108 ++++++++++++++++++++++++++++++ examples/dialogs_oeverview_app.py | 8 +++ remi/dialogs.py | 17 ++++- remi/res/info.png | Bin 0 -> 2551 bytes 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 artwork/info.svg create mode 100644 remi/res/info.png diff --git a/artwork/info.svg b/artwork/info.svg new file mode 100644 index 00000000..fcbb3a5b --- /dev/null +++ b/artwork/info.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + i + + diff --git a/examples/dialogs_oeverview_app.py b/examples/dialogs_oeverview_app.py index 616c977b..a4d02a31 100644 --- a/examples/dialogs_oeverview_app.py +++ b/examples/dialogs_oeverview_app.py @@ -27,17 +27,25 @@ def main(self): width=540, margin='0px auto', style={'display': 'block', 'overflow': 'hidden'}) + info_bt = gui.Button('Show info dialog', + width=200, height=30, margin='10px') + error_bt = gui.Button('Show error dialog', width=200, height=30, margin='10px') # setting the listener for the onclick event of the button + info_bt.set_on_click_listener(self.show_info_dialog) error_bt.set_on_click_listener(self.show_error_dialog) + verticalContainer.append(info_bt) verticalContainer.append(error_bt) # returning the root widget return verticalContainer + def show_info_dialog(self, widget): + dialogs.Info('Some information message', width=300).show(self) + def show_error_dialog(self, widget): dialogs.Error('Some error message', width=300).show(self) diff --git a/remi/dialogs.py b/remi/dialogs.py index b04170fd..49c85374 100644 --- a/remi/dialogs.py +++ b/remi/dialogs.py @@ -101,6 +101,20 @@ def hide(self): self._base_app_instance.set_root_widget(self._old_root_widget) +class Info(_DialogBase): + """Show a information dialog with a little message and button to accept. + + The Ok button emits the 'confirm_dialog' event. Register the listener to + it with set_on_confirm_dialog_listener. + """ + @decorate_constructor_parameter_types([str, Widget]) + def __init__(self, message='', **kwargs): + super(Info, self).__init__( + title='Information', + content=_make_content_(message, '/res/info.png'), + **kwargs) + + class Error(_DialogBase): """Show a error dialog with a little message and button to accept. @@ -115,7 +129,8 @@ def __init__(self, message='', **kwargs): title='Error', content=_make_content_(message, '/res/error.png'), **kwargs) - + + def _make_content_(message, icon_name): container = Widget() container.style['display'] = 'block' diff --git a/remi/res/info.png b/remi/res/info.png new file mode 100644 index 0000000000000000000000000000000000000000..c4c8496e407464fa40f48ca97920d753d0b71a28 GIT binary patch literal 2551 zcmVG|~c@;$$k3MMtSDCk-Wq75aNBIN>n-H0(2 z00@ymiEgw$gs=C5+yR68pj}UNB{8w-k4^_?ElPbK->;`GKE&*%0#j?!)WkAG+&mgh zE-yGe5TU0p%CXO*^bb{`QlBWW;-Uhr^ZRJ1N&*=m ziy()g0C*TQ0)s&yko5G$*!5mBZyugPe=3S#Guk`|_nrO{0N3Ay62GM`5$5_UC6~4g zAjJ@d6p#Wk7&1t*KprRnJ_cidfdUkU5XMA!{k>*h-g5!Tv_|W@;J!ai5^$r79C{MN zZCuwXSh)-wl>!E>4FM?(DFkUC3*>-2!g>+FAQ3o#iy@38LN*^`%dUBBf2SECeus{& zg*ywQQK-@3+M_sX8zFZU>sBkSTm(`=5YE7ay-L#dS-SMxkOi_Ba;4A<3J5$v<0_Af zXZ3M@LzZ`rUIW?XnB`kx=Q)t00B&?~)Hb4#ukz5E5HlN#FMNvmRt{>&SP!O4&;fYH z!sQX5=jzMmdi65PjTn9ofv&PJho;Gz{jSbOMTppA{oMG5JM7=B)&CBqAi@Ug(etc@tn`N`jT#xdIBD)I~P!?N2rU3Sh;L0 zI8kQBCkBBg8qRecAIzOPmEy>eQ=ID0^&vjW@u-Uo|Dt?R zV0|-!W8AY+acRp~fIiR<^p&oHetX|94|TdTcJFR8QVx8);;N^aH~j=YX$*N325=En zpow9KW2iz>g`f(kF4>L)b9naEnHX;t+<)A$k-LY6YM;e(#*m%B$JXQ0YMU(i5QJXX za_ZQ^mQ4uy(G1{I0J1337mcL#;Az{R@MHY&kg0YMf`5|05SgJ6Z-Scxj?r7T`WmMa$Yp~Meh zi*lfH8J<6dW%IrW4*M57WOJeFf9;&@qI1IYbHewt_W>4u%i{ zx9gK$-q?G>Z*Htq_4QGjni9;J6R3kw5+H)40-roVfd#M%fwsw@(I|X0J_e7V6Y>yv z%$}Cz?f))uP;wDXwd3y#1tF>v>d?1VcKzt1!L-;vSg1;Ob6(fcmV^zq9)TuElMn_6Syo)bxB+NQB8N6p9kh-T8E=GvKx!gD1*VkX zIfE$Mm88ND1G}`wMUu0@l}JJu!hj2?vp|DU;d*Ej9W}|vF)F~eQl%bn7|;lz5X9_a z5<|#Vt#2V53m8BeD_x?bv!#@CkTOj+pO_$^An;%w8MIO(JQ%}rlVOw}beGMQp1s5q&4oaM)cW|PB01-sc$p9XbksgG&^@w9(^0xaa-$_CkDy%m{ z1R)PVdy`?L>~Zk*A-a2KGvN~+0F?yN;8X5FDHM>f9&rh_vOM5fc@YFVk&Cq3oUo9_ zLiY4k;OYGizTU@?WxDQ92WbX%)1q4CZAWEH(84hv6!VTXGdoPJ2h#{#Y(99^pLhe2^1*N)S=syzL zHJ;wgYX|ZeGwS-|ge+@hrDLIrK!F#F9)$#kI1on@2P)B2Vu<3ID7z2G@%8VaT^GZ$ zfinY=e|JtEa0IN7Pyz|UTfB}0k6XQrA#AAk9BQC{CLQoU4Zj*lJ9KtcbG)aP(|xt1vNa>%0`P?WIbO=eTp`BRH>wfhDY*9p zlpj0I*Y}Y3)}l?A1=Gg{JJ`wmm;OR)wwu;$4_}K6a9v%78?K$h{JHf2)Ky59(Cc23 z`aIIo(-oWEdAN#>o%LtJamm#BpeLlXlSE4P(yn?Ae_BBEZFnUE=V;&K-$jV$`SEuI z7mfk>?gu=3X^xw}ck%cq*+B1&R!N_3@9BaMYaDJl)cFPM)aQuwo;THt?!qez= zcb9)R$}$^WoKy!Dk*j#%`p~G5U*@Q(T8eU%nNsTG@4I zInL7*U)i0uRP3(SuF`f@CO#&rHpFYu% z_h9R8c~EDmE}?-6+M^*qT`8sFSe zUJ#Q61bCzl8F?7Y?bIeTZC?q|Iv?uB*`=LM!rl*H$ALV(NeN;TnxDeGyjnnnAaSlsa8-Ehlm?Y%NC$F3EjPhBggZcPKI%u9z5?E@HfEypN)6L zNye&OLgF&eS0m&ST(t;aH==bE04dWb*+aoUi1GJH N002ovPDHLkV1n}LkW2so literal 0 HcmV?d00001 From b393ca07093bca80bb2cbc2a5fae47b951dadbc3 Mon Sep 17 00:00:00 2001 From: Int-0 Date: Mon, 1 Jan 2018 15:23:10 +0100 Subject: [PATCH 3/4] Adding canvas Widget --- examples/canvas.py | 57 +++++++++++++++++++++++++ remi/game/__init__.py | 19 +++++++++ remi/game/canvas.py | 42 ++++++++++++++++++ remi/game/color.py | 27 ++++++++++++ remi/game/draw.py | 70 ++++++++++++++++++++++++++++++ remi/game/rect.py | 99 +++++++++++++++++++++++++++++++++++++++++++ remi/server.py | 1 + setup.py | 4 +- 8 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 examples/canvas.py create mode 100644 remi/game/__init__.py create mode 100644 remi/game/canvas.py create mode 100644 remi/game/color.py create mode 100644 remi/game/draw.py create mode 100644 remi/game/rect.py diff --git a/examples/canvas.py b/examples/canvas.py new file mode 100644 index 00000000..e59ff3c9 --- /dev/null +++ b/examples/canvas.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +""" + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + +import os +import remi.gui as gui +from remi.game import Color, Rect +from remi.game.canvas import Canvas +from remi.game.draw import line, lines, circle, rect +from remi import start, App + + +class MyApp(App): + canvas = None + def __init__(self, *args): + super(MyApp, self).__init__(*args) + + def main(self, name='world'): + #margin 0px auto allows to center the app to the screen + container = gui.Widget(width=600, height=600) + self.canvas = Canvas(self, resolution=(600, 400), margin='0px auto') + button = gui.Button('Go!') + button.set_on_click_listener(self.draw) + container.append(self.canvas) + container.append(button) + # returning the root widget + return container + + def draw(self, widget): + line(self.canvas, Color(255, 0, 0), (0, 0), (200, 100)) + lines(self.canvas, Color(0, 0, 255), [(200, 100), + (100, 0), + (150, 400)]) + circle(self.canvas, Color(0, 255, 0), (200, 100), 10, width=1) + circle(self.canvas, Color(255, 0, 0), (300, 150), 10) + rect(self.canvas, Color(255, 255, 0), Rect((10, 10), (30, 30)), width=1) + rect(self.canvas, Color(128, 128, 255), Rect((5, 5), (15, 100))) + +if __name__ == "__main__": + print 'Starting with pid: %s' % os.getpid() + # starts the webserver + # optional parameters + # start(MyApp,address='127.0.0.1', port=8081, multiple_instance=False,enable_file_cache=True, update_interval=0.1, start_browser=True) + start(MyApp, debug=True) diff --git a/remi/game/__init__.py b/remi/game/__init__.py new file mode 100644 index 00000000..40a15e89 --- /dev/null +++ b/remi/game/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +""" + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + +from remi.game.rect import Rect +from remi.game.color import Color diff --git a/remi/game/canvas.py b/remi/game/canvas.py new file mode 100644 index 00000000..2562cbd5 --- /dev/null +++ b/remi/game/canvas.py @@ -0,0 +1,42 @@ +""" + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + +from remi.server import App +from remi.gui import Widget +from remi.gui import decorate_constructor_parameter_types + + +def _pixels_(amount): + if isinstance(amount, int): + return '%spx' % amount + else: + return amount + + +class Canvas(Widget): + @decorate_constructor_parameter_types([tuple, App]) + def __init__(self, app_instance, resolution=(0, 0), **kwargs): + kwargs['width'] = _pixels_(resolution[0]) + kwargs['height'] = _pixels_(resolution[1]) + super(Canvas, self).__init__(**kwargs) + self._app = app_instance + self.type = 'canvas' + + @property + def id(self): + return self.attributes['id'] + + def draw(self, js_code): + print js_code + self._app.execute_javascript(js_code) diff --git a/remi/game/color.py b/remi/game/color.py new file mode 100644 index 00000000..a63da61d --- /dev/null +++ b/remi/game/color.py @@ -0,0 +1,27 @@ +""" + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + +def _hex_(c): + return hex(c)[2:].zfill(2) + +class Color(object): + def __init__(self, r, g, b, a=255): + self.r = r + self.g = g + self.b = b + + def __str__(self): + return '#%s%s%s' % (_hex_(self.r), _hex_(self.g), _hex_(self.b)) + + diff --git a/remi/game/draw.py b/remi/game/draw.py new file mode 100644 index 00000000..657671d5 --- /dev/null +++ b/remi/game/draw.py @@ -0,0 +1,70 @@ +""" + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + +from remi.game.canvas import Canvas +from remi.game.color import Color + +def rect(canvas, color, rect, width=0): + canvas.draw('''var canvas = document.getElementById('%s'); +var ctx = canvas.getContext('2d'); +%s +ctx.%s(%s);%s''' % ( + canvas.id, + ('ctx.fillStyle = "%s";' % color) if width == 0 else ( + 'ctx.lineWidth = "%spx"; ctx.strokeStyle = "%s"' % (width, color)), + 'fillRect' if width == 0 else 'rect', + rect, + '' if width == 0 else 'ctx.stroke();' + )) + +def line(canvas, color, start_pos, end_pos, width=1): + canvas.draw('''var canvas = document.getElementById('%s'); +var ctx = canvas.getContext('2d'); +ctx.lineWidth = "%spx"; +ctx.strokeStyle = "%s"; +ctx.beginPath(); +ctx.moveTo(%s, %s); +ctx.lineTo(%s, %s); +ctx.stroke();''' % (canvas.id, width, color, + start_pos[0], start_pos[1], + end_pos[0], end_pos[1])) + +def lines(canvas, color, pointlist, width=1): + start = pointlist[0] + pointlist = pointlist[1:] + js = '''var canvas = document.getElementById('%s'); +var ctx = canvas.getContext('2d'); +ctx.lineWidth = "%spx"; +ctx.strokeStyle = "%s"; +ctx.beginPath(); +ctx.moveTo(%s, %s); +''' % (canvas.id, width, color, start[0], start[1]) + for point in pointlist: + js += 'ctx.lineTo(%s, %s);' % (point[0], point[1]) + js += 'ctx.stroke();' + canvas.draw(js) + +def circle(canvas, color, pos, radius, width=0): + canvas.draw('''var canvas = document.getElementById('%s'); +var ctx = canvas.getContext('2d'); +ctx.lineWidth = "%spx"; +ctx.strokeStyle = "%s";%s +ctx.beginPath(); +ctx.arc(%s, %s, %s, 0, 6.30);%s +ctx.stroke();''' % ( + canvas.id, + width, color, + ('ctx.fillStyle="%s";' % color) if (width == 0) else '', + pos[0], pos[1], radius, + 'ctx.fill();' if (width == 0) else '')) diff --git a/remi/game/rect.py b/remi/game/rect.py new file mode 100644 index 00000000..0e2d926c --- /dev/null +++ b/remi/game/rect.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +""" + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + + +class Rect(object): + def __init__(self, position, size): + self.x = position[0] + self.y = position[1] + self.w = size[0] + self.h = size[1] + + def __str__(self): + return '%s, %s, %s, %s' % (self.x, self.y, self.w, self.h) + + @property + def width(self): + return self.w + + @property + def height(self): + return self.h + + @property + def centerx(self): + return self.x + (self.w / 2) + + @property + def centery(self): + return self.y + (self.h / 2) + + @property + def center(self): + return (self.centerx, self.centery) + + @property + def size(self): + return (self.w, self.h) + + @property + def top(self): + return self.y + + @property + def bottom(self): + return self.y + self.h + + @property + def left(self): + return self.x + + @property + def right(self): + return self.x + self.w + + @property + def topleft(self): + return (self.left, self.top) + + @property + def bottomleft(self): + return (self.left, self.bottom) + + @property + def topright(self): + return (self.right, self.top) + + @property + def bottomright(self): + return (self.right, self.bottom) + + @property + def midtop(self): + return (self.centerx, self.top) + + @property + def midleft(self): + return (self.left, self.centery) + + @property + def midbottom(self): + return (self.centerx, self.bottom) + + @property + def midright(self): + return (self.right, self.centery) diff --git a/remi/server.py b/remi/server.py index cc215456..d0d91b4b 100644 --- a/remi/server.py +++ b/remi/server.py @@ -695,6 +695,7 @@ def set_root_widget(self, widget): def _send_spontaneous_websocket_message(self, message): global update_lock + self._log.debug('spontaneous message, lock: %s' % update_lock) with update_lock: for ws in self.client.websockets: # noinspection PyBroadException diff --git a/setup.py b/setup.py index cd2985be..440f1840 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ author='Davide Rosa', author_email='dddomodossola@gmail.com', license='Apache', - packages=['remi'], + packages=['remi', 'remi.game'], include_package_data=True, zip_safe=False, -) \ No newline at end of file +) From 8c10149ab22d22d69393d230a2a07ab8588220ed Mon Sep 17 00:00:00 2001 From: Int-0 Date: Mon, 1 Jan 2018 17:13:36 +0100 Subject: [PATCH 4/4] Adding basic raster drawing --- examples/canvas_raster.py | 51 +++++++++++++++++++++++++++++++++++++++ remi/game/canvas.py | 1 - remi/game/raster.py | 34 ++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 examples/canvas_raster.py create mode 100644 remi/game/raster.py diff --git a/examples/canvas_raster.py b/examples/canvas_raster.py new file mode 100644 index 00000000..0b942d01 --- /dev/null +++ b/examples/canvas_raster.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +""" + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + +import os +import remi.gui as gui +from remi.game import Color, Rect +from remi.game.canvas import Canvas +from remi.game.raster import load_image, draw +from remi import start, App + + +class MyApp(App): + canvas = None + def __init__(self, *args): + super(MyApp, self).__init__(*args) + + def main(self, name='world'): + #margin 0px auto allows to center the app to the screen + container = gui.Widget(width=600, height=600) + self.canvas = Canvas(self, resolution=(600, 400), margin='0px auto') + button = gui.Button('Go!') + button.set_on_click_listener(self.draw) + container.append(self.canvas) + container.append(button) + # returning the root widget + return container + + def draw(self, widget): + image = load_image('example.png') + draw(image, self.canvas, position=(10, 10)) + +if __name__ == "__main__": + print 'Starting with pid: %s' % os.getpid() + # starts the webserver + # optional parameters + # start(MyApp,address='127.0.0.1', port=8081, multiple_instance=False,enable_file_cache=True, update_interval=0.1, start_browser=True) + start(MyApp, debug=True) diff --git a/remi/game/canvas.py b/remi/game/canvas.py index 2562cbd5..9784da95 100644 --- a/remi/game/canvas.py +++ b/remi/game/canvas.py @@ -38,5 +38,4 @@ def id(self): return self.attributes['id'] def draw(self, js_code): - print js_code self._app.execute_javascript(js_code) diff --git a/remi/game/raster.py b/remi/game/raster.py new file mode 100644 index 00000000..6289c418 --- /dev/null +++ b/remi/game/raster.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# + +from PIL import Image + + +class RasterImage(object): + data = '' + width = 0 + height = 0 + + +def load_image(image_file): + image = Image.open(image_file) + data = image.tobytes() + data = [ord(b) for b in data] + result = RasterImage() + result.width = image.width + result.height = image.height + result.data = repr(data) + return result + + +def draw(image, canvas, position): + canvas.draw('''var canvas = document.getElementById('%s'); +var ctx = canvas.getContext('2d'); +var image = ctx.createImageData(%s, %s); +image.data.set(new Uint8ClampedArray(%s)); +ctx.putImageData(image, %s, %s);''' % ( + canvas.id, + image.width, image.height, image.data, + position[0], position[1] + ))