From 09c347650373fe55352fe36f9db151265a98e194 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 8 Sep 2020 23:30:11 +0100 Subject: [PATCH] Updated UI --- examples/icons/icon-backdrop.png | Bin 0 -> 1086 bytes examples/icons/icon-channel.png | Bin 0 -> 987 bytes examples/icons/icon-return.png | Bin 0 -> 1262 bytes examples/icons/icon-settings.png | Bin 1673 -> 1232 bytes examples/monitor.py | 261 +++++++++++++++++-------------- 5 files changed, 144 insertions(+), 117 deletions(-) create mode 100644 examples/icons/icon-backdrop.png create mode 100644 examples/icons/icon-channel.png create mode 100644 examples/icons/icon-return.png diff --git a/examples/icons/icon-backdrop.png b/examples/icons/icon-backdrop.png new file mode 100644 index 0000000000000000000000000000000000000000..ba9d25c18722d79fae3ea12070467f96258fafaf GIT binary patch literal 1086 zcmaJ=Pe>F|93D$c1WPEwEb1{{3U=q8>)K9k?zXdQ8?ETNpk2ao=IIVP^TwHHJ1!oy zLskaXrHj#_AgDuBR2Pdtq%boqFCiK9mu?maSw!_lUF*;`@aFyD`@Y}z`@P>g-PllH zT2fI$QB-OCkd!2Ig*%EjkpH#AN1l;sJC3&EX4HW*vIVKIirOKF>vAVdLRszWnS`|z zwZ2kIwc^&q5m7-pBfA*J(M`gpsMRf-DI9 z4ChriP5?ClhWGn?e$E2~PT*Lsn&kyA?-x0r$k%|?k0#M9wL?rwvDH{)7o^h|n9OCcB6QR#*<#fSxE zjI0zw-MK0@rV&PV8ks=g83F8R(F_%3?7ho`L_&-kHkJ(q#-$)lBp6LoMPEdUgneN? zCU8|e9}S0Vs{K((*e66|0l%-RD#w+O(yK!Q=eX)0u8^1O_JVGb$P%=)<4}!RNC(R~ zi&|bUfmmL>Rj!)f3zwJ65;LrOvwz)m?ufLHyIkvAvRUhVXpnZdNMAp>TiHb3#I3j# zPC0Y!YFpdVbVyt(nm~g~V%b9B+v!mM=((%EQsYAdFE8!dHu$*?Tq>B@dFNj7$&rFv zWkJunH(Nqaqnn3Aj{kw@`_RSE&)M?b8;*__f0(H#=lGe8h0mwx{n?^$_R1tc?m^X#gYVzrAu_bUfN1-8k5G;x|&OYRdos literal 0 HcmV?d00001 diff --git a/examples/icons/icon-channel.png b/examples/icons/icon-channel.png new file mode 100644 index 0000000000000000000000000000000000000000..3270941d5b2a45115dd84784defa672b09eba1cd GIT binary patch literal 987 zcmaJ=OK8+U7!C?jDiskgdT~fE7TnAvZMP;imfdEz4Kz#Lu67RSVjS_23$q%>2XmegAVdW=m58{U`e+ zNgA-Ht+LpUrmOFe_@DZ@q*%J`F*th!^P!H)Rp&UJ60fl?~%|nMA>0f+MIZ zC{3vYbe#V~gP@C!d713T`%mGxvKkRsCNW)Mu4r6sFm)9flZ;?aS%uQ&P8DYxl+(CD zBcQ2@22RiWp+}qXP>bL=h8@Nnhc2-#Qx+16?|X)&XEijc=ZYFas#?h9g`F!74_ifb z%*q$^4%edYNU8Hyj8A&(&aE)IlTSi1?o7<}hxc1g#hWPER<4q4ZQoaqNTBcF?I*i8hBhu= zI2K)62tNMYoWr%9%{v#*ZGTyK^Xle{@6X<{smz1t4?pcZg1^^~@7$_Cwh!08U3b6U aoeBGqbnn1|Lq3!3^tsuElC_z?vi=7R3p^+Q literal 0 HcmV?d00001 diff --git a/examples/icons/icon-return.png b/examples/icons/icon-return.png new file mode 100644 index 0000000000000000000000000000000000000000..717a6b463bbd872ea67604197cfe9d14db4a3b93 GIT binary patch literal 1262 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2v2cX64L~h>{3jAFJg2T)jk)8oi3#0-$aN1{?c|g2d$P)DnfH z)bz|eTc!8A_bVx6rr0WloBA5~7C5J7WO`H;r3P2|g(O#HCtIc{+1n}DR9FEG$W1Lt zRH(?!$t$+1uvG$^YXxM3g!Ppaz)DK8ZIvL7itr6kaLzAERWQ>t&`mZlFj6qL)H5_Q zF)=gHQ7|$vGSD|L)i*TKH8is_FtIW;SAYT~plwAdX;wilZcw{`JX@uVl9B=|ef{$C za=mh6z5JqdeM3u2OML?)eIp}XpbFjM%Dj@q3f;V7Wta&rsl~}fnFS@8`FRQ;6BCp2 zOG|8(fG&l2A-4c-Y+f-mn1BJMUy)d#Z>VPg@)As;uP=V3xw&xF#U(+h2=`(&xHzP; zAXPsowK%`DC>a=WY04n03ap%qQWHz^i$e1Ab6}wukda@KU!0L&pkQREXQZGJo|%`D zUtX*U*5K=F<(XGpl9-pA>gi&u1T;b~Gc(1?%-Gn#+}P2?&B)Ne(9qS<(ZJEw#nR2t z#nH^n+}Oeqrq?AuximL5uLPzy1)x~@uOUB$q_nCR)^ z7*cU-$&`&=%#H$W-%TVg99r<;sM?~8#f$73*lL zN~7%2ObJuYB<1Bl#nTe=W*ArhI(+=?ySsnyocUMnDACrol11!*c!zJ$O(zEv*mqV$dA&!q;+<&mp6K8eidzaadbJxT6Q+76y~ zkuKM!q<^~A9d%Os%o)$NJIfN{pBP=+ccJd)+=$J)f1Z7|c>&j}oVAal4kd49k((m? zktr%CcY4#=;)rFvUJ~1as!YAgKHrMsKOY?DVSRu3C7-&7HWfi<`VK0eRX?D@%V_aOzE!k(z(QX0m4Z+179RZRfUlq?T@yySQcYrDa9SHJU%x^YZ?Ev^&z+ bL?nTM-`Q2<$yBf9pc2T_)z4*}Q$iB}R}Q-4 literal 0 HcmV?d00001 diff --git a/examples/icons/icon-settings.png b/examples/icons/icon-settings.png index 712164dc795bd23328397e6c496c9f8c906b0e65..45805cd636e0cc21bff0aad9ff8d66bc7fbf9162 100644 GIT binary patch delta 564 zcmeC=y}&s^l7o$bfuXlkmUnV4v-(B{O~%Pr7?ma`F=kKBW730+w{6wyn`OtFIM zb-|@~@@ysp1*jrNoQhsE$-opj;Z&r~>{Nf$RN*ND1EaI2i(^Q|tu-Omc}$Kxa~*^W znWuR#;TLaEI$I>P!{yh7rTq(@a!qKmW#CO>_2Beyd?d{ILQHyZr%ZNP`p<}}SzmkJ zZcU$;+Y|5PG~YnMI=)Yp1a?8GV*B$U;xF3+W zCc&m&agXpbB?q4u3>$CEKUl`}_qdvf<)(z1rBjYq-`BKr-gnsWVYuZ+=hhD`K4k}Z zs?AnRk=;}4k_r%N(U-w^4res{*_rmw=;n$4oB2U%Xe zev(+k_)>mT$KAtGM|Yj)NI5@m>ihX7g&RKw9?F(~+MKu{ly91l>7kGdXStVv-O^r+5;&*8wNAWcIRk5_WM7&Lbn~%{XD_?p~jcy#qIw+4c(`0 kJNx5YTy5}gl>-bGT}$VET|4bIFnut1y85}Sb4q9e0Q26|JOBUy delta 1026 zcmcb>*~vRWQk9v3fnitpTYDhIlJ4m1$iT3%pZiZDE0E7v9OUlAuZ$78Vu<2M1nWUSnh9h=>TFX)-c0Kog{;rE_v}JUu<*6%61@Z9kq^72}w6wUpyNip9CnhH9>FJ4zifU_XM@B{hO#p)C=4POkKt@49L3nt0 zS63I%1M2GP6%`d38TA=JZ9uuErY0cS-rml{!~_%p`VYtj0-$ez+JJWR^YiEC<`xzf z0!>d&PEJTj0IF10R?g4QudJ*Dsw^uj1NwoFj}I7x6DLjtl0dz|&kz=l+_0E5ZGp&KmZmzcM*Ri zFMLEqNbt$SWVVe?jZS|1);UaL5cilB&3B@t%sbVigU!>}@yx`$bM<46f?>5uyqGZ)t8Y#Nk0ggZf5+9xgTIb;mzwf;HT;SA|0F3lLSwa& vfyySJ(^(7+ekicoIqwK}ndQwkg_)ruHaSU9UG_UL2pBwF{an^LB{Ts53|Ko~ diff --git a/examples/monitor.py b/examples/monitor.py index 335ca1b..c0a69ff 100644 --- a/examples/monitor.py +++ b/examples/monitor.py @@ -28,10 +28,11 @@ DISPLAY_WIDTH = 160 DISPLAY_HEIGHT = 80 COLOR_WHITE = (255, 255, 255) -COLOR_BLUE = (32, 137, 251) -COLOR_GREEN = (100, 255, 124) +COLOR_BLUE = (31, 137, 251) +COLOR_GREEN = (99, 255, 124) COLOR_YELLOW = (254, 219, 82) -COLOR_RED = (254, 82, 82) +COLOR_RED = (247, 0, 63) +COLOR_BLACK = (0, 0, 0) # Only the ALPHA channel is used from these images @@ -42,6 +43,9 @@ icon_alarm = Image.open("icons/icon-alarm.png").convert("RGBA") icon_snooze = Image.open("icons/icon-snooze.png").convert("RGBA") icon_help = Image.open("icons/icon-help.png").convert("RGBA") icon_settings = Image.open("icons/icon-settings.png").convert("RGBA") +icon_channel = Image.open("icons/icon-channel.png").convert("RGBA") +icon_backdrop = Image.open("icons/icon-backdrop.png").convert("RGBA") +icon_return = Image.open("icons/icon-return.png").convert("RGBA") class View: @@ -71,7 +75,7 @@ class View: pass def clear(self): - self._draw.rectangle((0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT), (255, 255, 255)) + self._draw.rectangle((0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT), (0, 0, 0)) def icon(self, icon, position, color): col = Image.new("RGBA", icon.size, color=color) @@ -80,7 +84,7 @@ class View: def label( self, position="X", - text="", + text=None, bgcolor=(0, 0, 0), textcolor=(255, 255, 255), margin=4, @@ -186,45 +190,48 @@ class MainView(View): View.__init__(self, image) def render_channel(self, channel): - x = [21, 61, 101][channel.channel - 1] + bar_x = 33 + bar_margin = 2 + bar_width = 30 + label_width = 16 + label_height = 16 + label_y = 0 + + x = [ + bar_x, + bar_x + ((bar_width + bar_margin) * 1), + bar_x + ((bar_width + bar_margin) * 2), + ][channel.channel - 1] # Saturation amounts from each sensor saturation = channel.sensor.saturation active = channel.sensor.active and channel.enabled warn_level = channel.warn_level - self._draw.rectangle((x, 0, x + 37, DISPLAY_HEIGHT), (230, 230, 230)) - if active: # Draw background bars self._draw.rectangle( - (x, int((1.0 - saturation) * DISPLAY_HEIGHT), x + 37, DISPLAY_HEIGHT), + (x, int((1.0 - saturation) * DISPLAY_HEIGHT), x + bar_width - 1, DISPLAY_HEIGHT), channel.indicator_color(saturation) if active else (229, 229, 229), ) y = int((1.0 - warn_level) * DISPLAY_HEIGHT) self._draw.rectangle( - (x, y, x + 37, y), (255, 0, 0) if channel.alarm else (0, 0, 0) + (x, y, x + bar_width - 1, y), (255, 0, 0) if channel.alarm else (0, 0, 0) ) # Channel selection icons - x += 11 - col = channel.indicator_color(saturation, channel.label_colours) - if channel.alarm: - self.icon(icon_alarm, (x - 2, 0), col if active else (129, 129, 129)) - else: - self._draw.rectangle( - (x, 2, x + 15, 17), - col if active else (129, 129, 129), - ) + x += (bar_width - label_width) // 2 + + self.icon(icon_channel, (x, label_y), (200, 200, 200) if active else (64, 64, 64)) # TODO: replace number text with graphic - tw, th = self.font.getsize("{}".format(channel.channel)) + tw, th = self.font.getsize(str(channel.channel)) self._draw.text( - (x + int(math.ceil(8 - (tw / 2.0))), 2), - "{}".format(channel.channel), + (x + int(math.ceil(8 - (tw / 2.0))), label_y + 1), + str(channel.channel), font=self.font, - fill=(255, 255, 255) if active else (200, 200, 200), + fill=(55, 55, 55) if active else (100, 100, 100), ) def render(self): @@ -233,15 +240,14 @@ class MainView(View): for channel in self.channels: self.render_channel(channel) - # Icon backdrops - self._draw.rectangle((0, 0, 19, 19), (32, 138, 251)) - # Icons - self.icon(icon_rightarrow, (0, 0), (255, 255, 255)) + self.icon(icon_backdrop, (0, 0), COLOR_WHITE) + self.icon(icon_rightarrow, (3, 3), (55, 55, 55)) self.alarm.render((0, DISPLAY_HEIGHT - 19)) - self.icon(icon_settings, (DISPLAY_WIDTH - 19, 0), COLOR_RED) + self.icon(icon_backdrop.rotate(180), (DISPLAY_WIDTH - 26, 0), COLOR_WHITE) + self.icon(icon_settings, (DISPLAY_WIDTH - 19 - 3, 3), (55, 55, 55)) class EditView(View): @@ -257,7 +263,8 @@ class EditView(View): View.__init__(self, image) def render(self): - self.label("X", "Done", textcolor=COLOR_WHITE, bgcolor=COLOR_RED) + self.icon(icon_backdrop.rotate(180), (DISPLAY_WIDTH - 26, 0), COLOR_WHITE) + self.icon(icon_return, (DISPLAY_WIDTH - 19 - 3, 3), (55, 55, 55)) option = self._options[self._current_option] title = option["title"] @@ -287,7 +294,7 @@ class EditView(View): self.icon(icon_help, (0, 0), COLOR_BLUE) - self._draw.text((3, 43), f"{title} : {text}", font=self.font, fill=(0, 0, 0)) + self._draw.text((3, 43), f"{title} : {text}", font=self.font, fill=COLOR_WHITE) if self._help_mode: self.overlay(help) @@ -369,7 +376,7 @@ class SettingsView(EditView): (23, 3), "Settings", font=self.font, - fill=(0, 0, 0), + fill=COLOR_WHITE, ) EditView.render(self) @@ -381,16 +388,14 @@ class ChannelView(View): self.channel = channel View.__init__(self, image) - def draw_status(self, subtle=False): - status = f"{self.channel.sensor.saturation * 100:.2f}% ({self.channel.sensor.moisture:.2f}Hz)" - - self._draw.rectangle((0, 20, DISPLAY_WIDTH, 40), (50, 50, 50)) + def draw_status(self, position): + status = f"Sat: {self.channel.sensor.saturation * 100:.2f}%" self._draw.text( - (3, 23), + position, status, font=self.font, - fill=(150, 150, 150) if subtle else (255, 255, 255), + fill=(255, 255, 255), ) @@ -402,65 +407,87 @@ class DetailView(ChannelView): """ def render(self): - width, height = self._image.size - self._draw.rectangle((0, 0, width, height), (255, 255, 255)) + self.clear() + if self.channel.enabled: + graph_height = DISPLAY_HEIGHT - 8 - 20 + graph_width = DISPLAY_WIDTH - 64 + + graph_x = (DISPLAY_WIDTH - graph_width) // 2 + graph_y = 8 + + self.draw_status((graph_x, graph_y + graph_height + 4)) + + self._draw.rectangle((graph_x, graph_y, graph_x + graph_width, graph_y + graph_height), (50, 50, 50)) + + for x, value in enumerate(self.channel.sensor.history[:graph_width]): + color = self.channel.indicator_color(value) + h = value * graph_height + x = graph_x + graph_width - x - 1 + self._draw.rectangle((x, graph_y + graph_height - h, x + 1, graph_y + graph_height), color) + + alarm_line = int(self.channel.warn_level * graph_height) + r = 255 + if self.channel.alarm: + r = int(((math.sin(time.time() * 3 * math.pi) + 1.0) / 2.0) * 128) + 127 + + self._draw.rectangle( + ( + 0, + graph_height + 8 - alarm_line, + DISPLAY_WIDTH - 40, + graph_height + 8 - alarm_line, + ), + (r, 0, 0), + ) + self._draw.rectangle( + ( + DISPLAY_WIDTH - 20, + graph_height + 8 - alarm_line, + DISPLAY_WIDTH, + graph_height + 8 - alarm_line, + ), + (r, 0, 0), + ) + + self.icon( + icon_alarm, + (DISPLAY_WIDTH - 40, graph_height + 8 - alarm_line - 10), + (r, 0, 0), + ) + + # Channel icons + + x_positions = [40, 72, 104] + label_x = x_positions[self.channel.channel - 1] + label_y = 0 + + active = self.channel.sensor.active and self.channel.enabled + + for x in x_positions: + self.icon(icon_channel, (x, label_y - 10), (16, 16, 16)) + + self.icon(icon_channel, (label_x, label_y), (200, 200, 200)) + + tw, th = self.font.getsize(str(self.channel.channel)) self._draw.text( - (23, 3), - "{}".format(self.channel.title), + (label_x + int(math.ceil(8 - (tw / 2.0))), label_y + 1), + str(self.channel.channel), font=self.font, - fill=(0, 0, 0), - ) - - self.draw_status() - - graph_height = DISPLAY_HEIGHT - 40 - self._draw.rectangle( - (0, DISPLAY_HEIGHT - graph_height, DISPLAY_WIDTH, DISPLAY_HEIGHT), - (10, 10, 10), - ) - - for x, value in enumerate(self.channel.sensor.history[:DISPLAY_WIDTH]): - color = self.channel.indicator_color(value) - h = value * graph_height - x = DISPLAY_WIDTH - x - 1 - self._draw.rectangle((x, DISPLAY_HEIGHT - h, x + 1, DISPLAY_HEIGHT), color) - - alarm_line = int(self.channel.warn_level * graph_height) - r = 255 - if self.channel.alarm: - r = int(((math.sin(time.time() * 3 * math.pi) + 1.0) / 2.0) * 128) + 127 - self._draw.rectangle( - ( - 0, - DISPLAY_HEIGHT - alarm_line, - DISPLAY_WIDTH - 40, - DISPLAY_HEIGHT - alarm_line, - ), - (r, 0, 0), - ) - self._draw.rectangle( - ( - DISPLAY_WIDTH - 20, - DISPLAY_HEIGHT - alarm_line, - DISPLAY_WIDTH, - DISPLAY_HEIGHT - alarm_line, - ), - (r, 0, 0), - ) - - self.icon( - icon_alarm, - (DISPLAY_WIDTH - 40, DISPLAY_HEIGHT - alarm_line - 10), - (r, 0, 0), + fill=(55, 55, 55) if active else (100, 100, 100), ) # Next button - self._draw.rectangle((0, 0, 19, 19), COLOR_BLUE) - self.icon(icon_rightarrow, (0, 0), COLOR_WHITE) + self.icon(icon_backdrop, (0, 0), COLOR_WHITE) + self.icon(icon_rightarrow, (3, 3), (55, 55, 55)) + + # Prev button + self.icon(icon_backdrop, (0, DISPLAY_HEIGHT - 26), COLOR_WHITE) + self.icon(icon_return, (3, DISPLAY_HEIGHT - 26 + 3), (55, 55, 55)) # Edit - self.label("X", "Edit", textcolor=(255, 255, 255), bgcolor=COLOR_RED) + self.icon(icon_backdrop.rotate(180), (DISPLAY_WIDTH - 26, 0), COLOR_WHITE) + self.icon(icon_settings, (DISPLAY_WIDTH - 19 - 3, 3), (55, 55, 55)) class ChannelEditView(ChannelView, EditView): @@ -515,29 +542,15 @@ class ChannelEditView(ChannelView, EditView): def render(self): self.clear() - self._draw.text( - (23, 3), "{}".format(self.channel.title), font=self.font, fill=(0, 0, 0) - ) - EditView.render(self) - if not self._help_mode: - self.draw_status(True) - class Channel: - bar_colours = [ - (192, 225, 254), # Blue - (196, 255, 209), # Green - (255, 243, 192), # Yellow - (254, 192, 192), # Red - ] - - label_colours = [ + colors = [ COLOR_BLUE, COLOR_GREEN, COLOR_YELLOW, - COLOR_RED, + COLOR_RED ] def __init__( @@ -603,22 +616,23 @@ class Channel: self._dry_point = dry_point self.sensor.set_dry_point(dry_point) - def indicator_color(self, value, r=None): + def warn_color(self): + value = self.sensor.moisture + + def indicator_color(self, value): value = 1.0 - value - if r is None: - r = self.bar_colours if value == 1.0: - return r[-1] + return self.colors[-1] if value == 0.0: - return r[0] + return self.colors[0] - value *= len(r) - 1 + value *= len(self.colors) - 1 a = int(math.floor(value)) b = a + 1 blend = float(value - a) - r, g, b = [int(((r[b][i] - r[a][i]) * blend) + r[a][i]) for i in range(3)] + r, g, b = [int(((self.colors[b][i] - self.colors[a][i]) * blend) + self.colors[a][i]) for i in range(3)] return (r, g, b) @@ -716,6 +730,7 @@ class Alarm(View): if self._sleep_until is not None: if self._sleep_until > time.time(): return + self._sleep_until = None if ( self.enabled @@ -743,7 +758,7 @@ class Alarm(View): def render(self, position=(0, 0)): x, y = position # Draw the snooze icon- will be pulsing red if the alarm state is True - self._draw.rectangle((x, y, x + 19, y + 19), (255, 255, 255)) + #self._draw.rectangle((x, y, x + 19, y + 19), (255, 255, 255)) r = 129 if self._triggered and self._sleep_until is None: r = int(((math.sin(time.time() * 3 * math.pi) + 1.0) / 2.0) * 128) + 127 @@ -777,6 +792,10 @@ class ViewController: self.views = views self._current_view = 0 self._current_subview = 0 + + @property + def home(self): + return self._current_view == 0 and self._current_subview == 0 def next_subview(self): view = self.views[self._current_view] @@ -790,6 +809,12 @@ class ViewController: self._current_view %= len(self.views) self._current_subview = 0 + def prev_view(self): + if self._current_subview == 0: + self._current_view -= 1 + self._current_view %= len(self.views) + self._current_subview = 0 + def get_current_view(self): view = self.views[self._current_view] if isinstance(view, tuple): @@ -812,7 +837,8 @@ class ViewController: self.next_view() def button_b(self): - return self.view.button_b() + if not self.view.button_b(): + self.prev_view() def button_x(self): if not self.view.button_x(): @@ -904,10 +930,11 @@ def main(): if label == "B": # Sleep Alarm if not viewcontroller.button_b(): - if alarm.sleeping(): - alarm.cancel_sleep() - else: - alarm.sleep() + if viewcontroller.home: + if alarm.sleeping(): + alarm.cancel_sleep() + else: + alarm.sleep() if label == "X": viewcontroller.button_x()