import a bunch of old python stuff for testing

This commit is contained in:
K. Lange 2018-09-25 15:34:44 +09:00
parent 1af8a04b14
commit 1fe143ebea
8 changed files with 1598 additions and 0 deletions

View File

@ -0,0 +1,80 @@
#!/usr/bin/python3
"""
Generic "About" applet provider.
"""
import sys
import cairo
import yutani
import text_region
import toaru_fonts
import yutani_mainloop
class AboutAppletWindow(yutani.Window):
base_width = 350
base_height = 250
text_offset = 110
def __init__(self, decorator, title, logo, text, icon="star",on_close=None):
super(AboutAppletWindow, self).__init__(self.base_width + decorator.width(), self.base_height + decorator.height(), title=title, icon=icon, doublebuffer=True)
self.move(int((yutani.yutani_ctx._ptr.contents.display_width-self.width)/2),int((yutani.yutani_ctx._ptr.contents.display_height-self.height)/2))
self.decorator = decorator
#self.logo = cairo.ImageSurface.create_from_png(logo)
self.font = toaru_fonts.Font(toaru_fonts.FONT_SANS_SERIF, 13, 0xFF000000)
self.tr = text_region.TextRegion(0,0,self.base_width-30,self.base_height-self.text_offset,font=self.font)
self.tr.set_alignment(2)
self.tr.set_richtext(text)
self.on_close = on_close
self.draw()
def draw(self):
surface = self.get_cairo_surface()
WIDTH, HEIGHT = self.width - self.decorator.width(), self.height - self.decorator.height()
ctx = cairo.Context(surface)
ctx.translate(self.decorator.left_width(), self.decorator.top_height())
ctx.rectangle(0,0,WIDTH,HEIGHT)
ctx.set_source_rgb(204/255,204/255,204/255)
ctx.fill()
#ctx.set_source_surface(self.logo,int((WIDTH-self.logo.get_width())/2),10+int((84-self.logo.get_height())/2))
#ctx.paint()
self.tr.resize(WIDTH-30,HEIGHT-self.text_offset)
self.tr.move(self.decorator.left_width() + 15,self.decorator.top_height()+self.text_offset)
self.tr.draw(self)
self.decorator.render(self)
self.flip()
def finish_resize(self, msg):
"""Accept a resize."""
self.resize_accept(msg.width, msg.height)
self.reinit()
self.draw()
self.resize_done()
self.flip()
def close_window(self):
self.close()
if self.on_close:
self.on_close()
def mouse_event(self, msg):
if self.decorator.handle_event(msg) == yutani.Decor.EVENT_CLOSE:
self.close_window()
return
x,y = msg.new_x - self.decorator.left_width(), msg.new_y - self.decorator.top_height()
w,h = self.width - self.decorator.width(), self.height - self.decorator.height()
def keyboard_event(self, msg):
if msg.event.key == b"q":
self.close_window()

View File

@ -0,0 +1,310 @@
#!/usr/bin/python3
"""
Fancy clock.
"""
import math
import os
import sys
import time
import cairo
import yutani
import toaru_fonts
import fswait
from menu_bar import MenuBarWidget, MenuEntryAction, MenuEntrySubmenu, MenuEntryDivider, MenuWindow
from about_applet import AboutAppletWindow
import yutani_mainloop
app_name = "Clock"
version = "1.0.0"
_description = f"<b>{app_name} {version}</b>\n© 2017 Kevin Lange\n\nAnalog clock widget.\n\n<color 0x0000FF>http://github.com/klange/toaruos</color>"
class BasicWatchFace(object):
def __init__(self):
self.font = toaru_fonts.get_cairo_face()
def draw_background(self, ctx, t):
ctx.set_line_width(9)
ctx.set_source_rgb(0,0,0)
ctx.arc(0,0,100 - 10, 0, 2 * math.pi)
ctx.stroke_preserve()
ctx.set_source_rgb(1,1,1)
ctx.fill()
r2 = 100 - 9
ctx.set_source_rgb(0,0,0)
for i in range(12*5):
theta = 2 * math.pi * (i / (12*5))
if i % 5 == 0:
ctx.set_line_width(2)
r1 = 100 - 20
else:
ctx.set_line_width(0.5)
r1 = 100 - 14
ctx.move_to(math.sin(theta) * r1, -r1 * math.cos(theta))
ctx.line_to(math.sin(theta) * r2, -r2 * math.cos(theta))
ctx.stroke()
def setup_labels(self, ctx, t):
ctx.set_font_face(self.font)
ctx.set_font_size(12)
ctx.set_source_rgb(0,0,0)
def draw_labels(self, ctx, t):
ctx.save()
label = "12"
e = ctx.text_extents(label)
ctx.move_to(-e[2]/2,-72+e[3])
ctx.show_text(label)
ctx.fill()
label = "3"
e = ctx.text_extents(label)
ctx.move_to(75-e[2],e[3]/2)
ctx.show_text(label)
ctx.fill()
label = "9"
e = ctx.text_extents(label)
ctx.move_to(-75,e[3]/2)
ctx.show_text(label)
ctx.fill()
label = "6"
e = ctx.text_extents(label)
ctx.move_to(-e[2]/2,72)
ctx.show_text(label)
ctx.fill()
label = "1"
e = ctx.text_extents(label)
ctx.move_to(39-e[2],-63+e[3])
ctx.show_text(label)
ctx.fill()
label = "11"
e = ctx.text_extents(label)
ctx.move_to(-39,-63+e[3])
ctx.show_text(label)
ctx.fill()
label = "5"
e = ctx.text_extents(label)
ctx.move_to(39-e[2],63)
ctx.show_text(label)
ctx.fill()
label = "7"
e = ctx.text_extents(label)
ctx.move_to(-39,63)
ctx.show_text(label)
ctx.fill()
label = "2"
e = ctx.text_extents(label)
ctx.move_to(63-e[2],-37+e[3])
ctx.show_text(label)
ctx.fill()
label = "10"
e = ctx.text_extents(label)
ctx.move_to(-63,-37+e[3])
ctx.show_text(label)
ctx.fill()
label = "4"
e = ctx.text_extents(label)
ctx.move_to(63-e[2],37)
ctx.show_text(label)
ctx.fill()
label = "8"
e = ctx.text_extents(label)
ctx.move_to(-63,37)
ctx.show_text(label)
ctx.fill()
ctx.restore()
def draw_date(self, ctx, t):
ctx.save()
ctx.set_font_size(10)
label = time.strftime('%B %e',t[0])
e = ctx.text_extents(label)
ctx.move_to(-e[2]/2,-30)
ctx.show_text(label)
ctx.fill()
def draw_line(self, ctx, thickness, color, a, b, r1, r2):
theta = (a / b) * 2 * math.pi
ctx.set_line_width(thickness)
ctx.set_source_rgb(*color)
ctx.move_to(math.sin(theta) * r1, -r1 * math.cos(theta))
ctx.line_to(math.sin(theta) * r2, -r2 * math.cos(theta))
ctx.stroke()
def tick(self,t):
ts = t*t
tc = ts*t
return (0.5*tc*ts + -8*ts*ts + 20*tc + -19*ts + 7.5*t);
def draw_hands(self, ctx, t):
_,h,m,s = t
self.draw_line(ctx,3,(0,0,0),h%12+(m+s/60)/60,12,52,-5)
self.draw_line(ctx,2,(0,0,0),m+s/60,60,86,-10)
_s = int(60+s-1)+self.tick(s%1)
self.draw_line(ctx,1,(1,0,0),_s,60,86,-20)
self.draw_line(ctx,3,(1,0,0),_s,60,-4,-16)
def draw(self, ctx, t):
self.draw_background(ctx,t)
self.setup_labels(ctx,t)
self.draw_labels(ctx,t)
self.draw_date(ctx,t)
self.draw_hands(ctx,t)
class DarkWatchFace(BasicWatchFace):
def draw_background(self, ctx, t):
ctx.set_line_width(9)
ctx.set_source_rgb(1,1,1)
ctx.arc(0,0,100 - 10, 0, 2 * math.pi)
ctx.stroke_preserve()
ctx.set_source_rgb(0,0,0)
ctx.fill()
r2 = 100 - 9
ctx.set_source_rgb(1,1,1)
for i in range(12*5):
theta = 2 * math.pi * (i / (12*5))
if i % 5 == 0:
ctx.set_line_width(2)
r1 = 100 - 20
else:
ctx.set_line_width(0.5)
r1 = 100 - 14
ctx.move_to(math.sin(theta) * r1, -r1 * math.cos(theta))
ctx.line_to(math.sin(theta) * r2, -r2 * math.cos(theta))
ctx.stroke()
def draw_hands(self, ctx, t):
_,h,m,s = t
self.draw_line(ctx,3,(1,1,1),h%12+(m+s/60)/60,12,52,-5)
self.draw_line(ctx,2,(1,1,1),m+s/60,60,86,-10)
_s = int(60+s-1)+self.tick(s%1)
self.draw_line(ctx,1,(1,0,0),_s,60,86,-20)
self.draw_line(ctx,3,(1,0,0),_s,60,-4,-16)
def setup_labels(self, ctx, t):
ctx.set_font_face(self.font)
ctx.set_font_size(12)
ctx.set_source_rgb(1,1,1)
class ClockWindow(yutani.Window):
base_width = 200
base_height = 200
def __init__(self):
super(ClockWindow, self).__init__(self.base_width, self.base_height, title="Clock", icon="clock", doublebuffer=True)
self.move(100,100)
self.update_shape(yutani.WindowShape.THRESHOLD_CLEAR)
self.menus = {}
self.watchfaces = {
'Default': BasicWatchFace(),
'Dark': DarkWatchFace(),
}
self.watchface = self.watchfaces['Default']
def draw(self):
surface = self.get_cairo_surface()
ctx = cairo.Context(surface)
# Clear
ctx.set_operator(cairo.OPERATOR_SOURCE)
ctx.rectangle(0,0,self.width,self.height)
ctx.set_source_rgba(0,0,0,0)
ctx.fill()
ctx.set_operator(cairo.OPERATOR_OVER)
ctx.translate(self.width / 2, self.height / 2)
ctx.scale(self.width / 200, self.height / 200)
current_time = time.time()
t = time.localtime(int(current_time))
s = current_time % 60
m = t.tm_min
h = t.tm_hour
self.watchface.draw(ctx,(t,h,m,s))
self.flip()
def finish_resize(self, msg):
"""Accept a resize."""
if msg.width != msg.height:
s = min(msg.width,msg.height)
self.resize_offer(s,s)
return
self.resize_accept(msg.width, msg.height)
self.reinit()
self.draw()
self.resize_done()
self.flip()
def exit(self, data):
sys.exit(0)
def about(self, data=None):
AboutAppletWindow(d,f"About {app_name}","/usr/share/icons/48/clock.png",_description,"clock")
def mouse_event(self, msg):
# drag start
if msg.command == yutani.MouseEvent.DOWN and msg.buttons & yutani.MouseButton.BUTTON_LEFT:
self.drag_start()
if msg.buttons & yutani.MouseButton.BUTTON_RIGHT:
if not self.menus:
def set_face(x):
self.watchface = self.watchfaces[x]
faces = [MenuEntryAction(x,None,set_face,x) for x in self.watchfaces.keys()]
menu_entries = [
MenuEntrySubmenu("Watchface",faces,icon='clock'),
MenuEntryAction(f"About {app_name}","star",self.about,None),
MenuEntryDivider(),
MenuEntryAction("Exit","exit",self.exit,None),
]
menu = MenuWindow(menu_entries,(self.x+msg.new_x,self.y+msg.new_y),root=self)
def keyboard_event(self, msg):
if msg.event.action != yutani.KeyAction.ACTION_DOWN:
return
if msg.event.key == b'q':
self.exit(None)
if __name__ == '__main__':
yutani.Yutani()
d = yutani.Decor() # Just in case.
window = ClockWindow()
window.draw()
fds = [yutani.yutani_ctx]
while 1:
# Poll for events.
fd = fswait.fswait(fds,20)
if fd == 0:
msg = yutani.yutani_ctx.poll()
while msg:
yutani_mainloop.handle_event(msg)
msg = yutani.yutani_ctx.poll(False)
window.draw()
elif fd == 1:
# Tick
window.draw()

View File

@ -0,0 +1,20 @@
import ctypes
_lib = None
if not _lib:
_lib = ctypes.CDLL('libc.so')
_lib.syscall_fswait.argtypes = [ctypes.c_int,ctypes.POINTER(ctypes.c_int)]
_lib.syscall_fswait.restype = ctypes.c_int
_lib.syscall_fswait2.argtypes = [ctypes.c_int,ctypes.POINTER(ctypes.c_int),ctypes.c_int]
_lib.syscall_fswait2.restype = ctypes.c_int
def fswait(files,timeout=None):
fds = (ctypes.c_int * len(files))()
for i in range(len(files)):
fds[i] = files[i].fileno()
if timeout is None:
return _lib.syscall_fswait(len(fds),fds)
else:
return _lib.syscall_fswait2(len(fds),fds,timeout)

View File

@ -0,0 +1,413 @@
"""
Provides basic nested menus.
"""
import cairo
import math
import yutani
import text_region
import toaru_fonts
#from icon_cache import get_icon
menu_windows = {}
def close_enough(msg):
return msg.command == yutani.MouseEvent.RAISE and \
math.sqrt((msg.new_x - msg.old_x) ** 2 + (msg.new_y - msg.old_y) ** 2) < 10
class MenuBarWidget(object):
"""Widget for display multiple menus."""
height = 24
hilight_gradient_top = (93/255,163/255,236/255)
hilight_gradient_bottom = (56/255,137/255,220/255)
def __init__(self, window, entries):
self.window = window
self.entries = entries
self.font = toaru_fonts.Font(toaru_fonts.FONT_SANS_SERIF,13,0xFFFFFFFF)
self.active_menu = None
self.active_entry = None
self.gradient = cairo.LinearGradient(0,0,0,self.height)
self.gradient.add_color_stop_rgba(0.0,*self.hilight_gradient_top,1.0)
self.gradient.add_color_stop_rgba(1.0,*self.hilight_gradient_bottom,1.0)
def draw(self, cr, x, y, width):
self.x = x
self.y = y
self.width = width
cr.save()
cr.set_source_rgb(59/255,59/255,59/255)
cr.rectangle(0,0,width,self.height)
cr.fill()
cr.restore()
menus = self.window.menus
x_, y_ = cr.user_to_device(x,y)
offset = 0
for e in self.entries:
title, _ = e
w = self.font.width(title) + 10
if self.active_menu in menus.values() and e == self.active_entry:
cr.rectangle(offset+2,0,w+2,self.height)
cr.set_source(self.gradient)
cr.fill()
tr = text_region.TextRegion(int(x_)+8+offset,int(y_)+2,w,self.height,self.font)
tr.set_one_line()
tr.set_text(title)
tr.draw(self.window)
offset += w
def mouse_event(self, msg, x, y):
offset = 0
for e in self.entries:
title, menu = e
w = self.font.width(title) + 10
if x >= offset and x < offset + w:
if msg.command == yutani.MouseEvent.CLICK or close_enough(msg):
menu = MenuWindow(menu,(self.window.x+self.window.decorator.left_width()+offset,self.window.y+self.window.decorator.top_height()+self.height),root=self.window)
self.active_menu = menu
self.active_entry = e
elif self.active_menu and self.active_menu in self.window.menus.values() and e != self.active_entry:
self.active_menu.definitely_close()
menu = MenuWindow(menu,(self.window.x+self.window.decorator.left_width()+offset,self.window.y+self.window.decorator.top_height()+self.height),root=self.window)
self.active_menu = menu
self.active_entry = e
self.window.draw()
break
offset += w
class MenuEntryAction(object):
"""Menu entry class for describing a menu entry with an action."""
# This should be calculated from the space necessary for the icon,
# but we're going to be lazy for now and just assume they're all this big.
height = 20
hilight_border_top = (54/255,128/255,205/255)
hilight_gradient_top = (93/255,163/255,236/255)
hilight_gradient_bottom = (56/255,137/255,220/55)
hilight_border_bottom = (47/255,106/255,167/255)
right_margin = 50
def __init__(self, title, icon, action=None, data=None, rich=False):
self.title = title
self.icon = None #get_icon(icon,16) if icon else None
self.action = action
self.data = data
self.font = toaru_fonts.Font(toaru_fonts.FONT_SANS_SERIF,13,0xFF000000)
self.font_hilight = toaru_fonts.Font(toaru_fonts.FONT_SANS_SERIF,13,0xFFFFFFFF)
self.rich = rich
self.width = self.font.width(self.title) + self.right_margin # Arbitrary bit of extra space.
# Fit width to hold title?
self.tr = text_region.TextRegion(0,0,self.width - 22, 20, self.font)
self.update_text()
self.hilight = False
self.window = None
self.gradient = cairo.LinearGradient(0,0,0,self.height-2)
self.gradient.add_color_stop_rgba(0.0,*self.hilight_gradient_top,1.0)
self.gradient.add_color_stop_rgba(1.0,*self.hilight_gradient_bottom,1.0)
def update_text(self):
if self.rich:
self.tr.set_richtext(self.title)
else:
self.tr.set_text(self.title)
self.width = self.font.width(self.title) + self.right_margin # Arbitrary bit of extra space.
if self.rich:
try:
self.width = self.tr.get_offset_at_index(-1)[1][1] + self.right_margin
except:
pass # uh, fallback to the original width
self.tr.resize(self.width - 22, 20)
def focus_enter(self,keyboard=False):
if self.window and self.window.child:
self.window.child.definitely_close()
self.tr.set_font(self.font_hilight)
self.update_text()
self.hilight = True
def focus_leave(self):
self.tr.set_font(self.font)
self.update_text()
self.hilight = False
def draw(self, window, offset, ctx):
# Here, offset = y offset, not x like in panel widgets
# eventually, this all needs to be made generic with containers and calculated window coordinates...
# but for now, as always, we're being lazy
self.window = window
self.offset = offset
if self.hilight:
ctx.rectangle(1,offset,window.width-2,1)
ctx.set_source_rgb(*self.hilight_border_top)
ctx.fill()
ctx.rectangle(1,offset+self.height-1,window.width-2,1)
ctx.set_source_rgb(*self.hilight_border_bottom)
ctx.fill()
ctx.save()
ctx.translate(0,offset+1)
ctx.rectangle(1,0,window.width-2,self.height-2)
ctx.set_source(self.gradient)
ctx.fill()
ctx.restore()
if self.icon:
ctx.save()
ctx.translate(4,offset+2)
if self.icon.get_width != 16:
ctx.scale(16/self.icon.get_width(),16/self.icon.get_width())
ctx.set_source_surface(self.icon,0,0)
ctx.paint()
ctx.restore()
self.tr.move(22,offset+2)
self.tr.draw(window)
def activate(self):
if self.action:
self.action(self.data) # Probably like launch_app("terminal")
self.window.root.hovered_menu = None
self.focus_leave()
m = [m for m in self.window.root.menus.values()]
for k in m:
k.definitely_close()
self.window.root.draw()
def close_enough(self, msg):
return close_enough(msg) and msg.old_y >= self.offset and msg.old_y < self.offset + self.height
def mouse_action(self, msg):
if msg.command == yutani.MouseEvent.CLICK or self.close_enough(msg):
self.activate()
return False
class MenuEntrySubmenu(MenuEntryAction):
"""A menu entry which opens a nested submenu."""
def __init__(self, title, entries, icon="folder"):
super(MenuEntrySubmenu,self).__init__(title,icon,None,None)
self.entries = entries
def focus_enter(self,keyboard=False):
self.tr.set_font(self.font_hilight)
self.tr.set_text(self.title)
self.hilight = True
if not keyboard:
self.activate()
def activate(self):
if self.window:
menu = MenuWindow(self.entries, (self.window.x + self.window.width - 2, self.window.y + self.offset - self.window.top_height), self.window, root=self.window.root)
def mouse_action(self, msg):
return False
class MenuEntryDivider(object):
"""A visible separator between menu entries. Does nothing."""
height = 6
width = 0
def draw(self, window, offset, ctx):
self.window = window
ctx.rectangle(2,offset+3,window.width-4,1)
ctx.set_source_rgb(0.7,0.7,0.7)
ctx.fill()
ctx.rectangle(2,offset+4,window.width-5,1)
ctx.set_source_rgb(0.98,0.98,0.98)
ctx.fill()
def focus_enter(self,keyboard=False):
if self.window and self.window.child:
self.window.child.definitely_close()
pass
def focus_leave(self):
pass
def mouse_action(self,msg):
return False
class MenuWindow(yutani.Window):
"""Nested menu window."""
# These should be part of some theming functionality, but for now we'll
# just embed them here in the MenuWindow class.
top_height = 4
bottom_height = 4
base_background = (239/255,238/255,232/255)
base_border = (109/255,111/255,112/255)
def __init__(self, entries, origin=(0,0), parent=None, root=None):
self.parent = parent
if self.parent:
self.parent.child = self
self.entries = entries
required_width = max([e.width for e in entries])
required_height = sum([e.height for e in entries]) + self.top_height + self.bottom_height
flags = yutani.WindowFlag.FLAG_ALT_ANIMATION
super(MenuWindow, self).__init__(required_width,required_height,doublebuffer=True,flags=flags)
menu_windows[self.wid] = self
self.move(*origin)
self.focused_widget = None
self.child = None
self.x, self.y = origin
self.closed = False
self.root = root
self.root.menus[self.wid] = self
self.draw()
def draw(self):
surface = self.get_cairo_surface()
ctx = cairo.Context(surface)
ctx.set_operator(cairo.OPERATOR_SOURCE)
ctx.rectangle(0,0,self.width,self.height)
ctx.set_line_width(2)
ctx.set_source_rgb(*self.base_background)
ctx.fill_preserve()
ctx.set_source_rgb(*self.base_border)
ctx.stroke()
ctx.set_operator(cairo.OPERATOR_OVER)
offset = self.top_height
for entry in self.entries:
entry.draw(self,offset,ctx)
offset += entry.height
self.flip()
def mouse_action(self, msg):
if msg.new_y < self.top_height or msg.new_y >= self.height - self.bottom_height or \
msg.new_x < 0 or msg.new_x >= self.width:
if self.focused_widget:
self.focused_widget.focus_leave()
self.focused_widget = None
self.draw()
return
# We must have focused something
x = (msg.new_y - self.top_height)
offset = 0
new_widget = None
for entry in self.entries:
if x >= offset and x < offset + entry.height:
new_widget = entry
break
offset += entry.height
redraw = False
if new_widget:
if self.focused_widget != new_widget:
if self.focused_widget:
self.focused_widget.focus_leave()
new_widget.focus_enter(keyboard=False)
self.focused_widget = new_widget
redraw = True
if new_widget.mouse_action(msg):
redraw = True
if redraw:
self.draw()
def has_eventual_child(self, child):
"""Does this menu have the given menu as a child, or a child of a child, etc.?"""
if child is self: return True
if not self.child: return False
return self.child.has_eventual_child(child)
def keyboard_event(self, msg):
"""Handle keyboard."""
if msg.event.action != yutani.KeyAction.ACTION_DOWN:
return
if msg.event.keycode == yutani.Keycode.ESCAPE:
self.root.hovered_menu = None
self.root.menus[msg.wid].leave_menu()
return
self.root.hovered_menu = self
if msg.event.keycode == yutani.Keycode.ARROW_DOWN:
if not self.focused_widget and self.entries:
self.focused_widget = self.entries[0]
self.focused_widget.focus_enter(keyboard=True)
self.draw()
return
i = 0
for entry in self.entries:
if entry == self.focused_widget:
break
i += 1
i += 1
if i >= len(self.entries):
i = 0
self.focused_widget.focus_leave()
self.focused_widget = self.entries[i]
self.focused_widget.focus_enter(keyboard=True)
self.draw()
if msg.event.keycode == yutani.Keycode.ARROW_UP:
if not self.focused_widget and self.entries:
self.focused_widget = self.entries[0]
self.focused_widget.focus_enter(keyboard=True)
self.draw()
return
i = 0
for entry in self.entries:
if entry == self.focused_widget:
break
i += 1
i -= 1
if i < 0:
i = len(self.entries)-1
self.focused_widget.focus_leave()
self.focused_widget = self.entries[i]
self.focused_widget.focus_enter(keyboard=True)
self.draw()
if msg.event.keycode == yutani.Keycode.ARROW_LEFT:
self.root.hovered_menu = self.parent
self.definitely_close()
return
if msg.event.keycode == yutani.Keycode.ARROW_RIGHT:
if self.focused_widget and isinstance(self.focused_widget, MenuEntrySubmenu):
self.focused_widget.activate()
if msg.event.key == b'\n':
if self.focused_widget:
self.focused_widget.activate()
def close(self):
if self.wid in menu_windows:
del menu_windows[self.wid]
super(MenuWindow,self).close()
def definitely_close(self):
"""Close this menu and all of its submenus."""
if self.child:
self.child.definitely_close()
self.child = None
if self.closed:
return
if self.focused_widget:
self.focused_widget.focus_leave()
self.closed = True
wid = self.wid
self.close()
del self.root.menus[wid]
def leave_menu(self):
"""Focus has left this menu. If it is not a parent of the currently active menu, close it."""
if self.has_eventual_child(self.root.hovered_menu):
pass
else:
m = [m for m in self.root.menus.values()]
for k in m:
if not self.root.hovered_menu or (k is not self.root.hovered_menu.child and not k.has_eventual_child(self.root.hovered_menu)):
k.definitely_close()

View File

@ -0,0 +1,581 @@
import hashlib
import subprocess
from urllib.parse import urlparse
import unicodedata
from html.parser import HTMLParser
import math
import os
import cairo
import toaru_fonts
_emoji_available = os.path.exists('/usr/share/emoji')
if _emoji_available:
_emoji_values = [int(x.replace('.png',''),16) for x in os.listdir('/usr/share/emoji') if x.endswith('.png') and not '-' in x]
_emoji_table = {}
def get_emoji(emoji):
if not emoji in _emoji_table:
_emoji_table[emoji] = cairo.ImageSurface.create_from_png('/usr/share/emoji/' + hex(ord(emoji)).replace('0x','')+'.png')
return _emoji_table[emoji]
class TextUnit(object):
def __init__(self, string, unit_type, font):
self.string = string
self.unit_type = unit_type
self.font = font
self.width = font.width(self.string) if font else 0
self.extra = {}
self.tag_group = None
if self.unit_type == 2 and _emoji_available:
if ord(self.string) > 0x1000 and ord(self.string) in _emoji_values:
self.extra['emoji'] = True
self.extra['img'] = get_emoji(self.string)
self.extra['offset'] = font.font_size
self.string = ""
self.width = font.font_size
def set_tag_group(self, tag_group):
self.tag_group = tag_group
self.tag_group.append(self)
def set_font(self, font):
if 'img' in self.extra: return
self.font = font
self.width = font.width(self.string) if font else 0
def set_extra(self, key, data):
self.extra[key] = data
def __repr__(self):
return "(" + self.string + "," + str(self.unit_type) + "," + str(self.width) + ")"
class TextRegion(object):
def __init__(self, x, y, width, height, font=None):
self.x = x
self.y = y
self.width = width
self.height = height
if not font:
font = toaru_fonts.Font(toaru_fonts.FONT_SANS_SERIF, 13)
self.font = font
self.text = ""
self.lines = []
self.align = 0
self.valign = 0
self.line_height = self.font.font_size
self.text_units = []
self.scroll = 0
self.ellipsis = ""
self.one_line = False
self.base_dir = ""
self.break_all = False
self.title = None
self.max_lines = None
def set_alignment(self, align):
self.align = align
def set_valignment(self, align):
self.valign = align
def set_max_lines(self, max_lines):
self.max_lines = max_lines
self.reflow()
def visible_lines(self):
return int(self.height / self.line_height)
def reflow(self):
self.lines = []
current_width = 0
current_units = []
leftover = None
i = 0
while i < len(self.text_units):
if leftover:
unit = leftover
leftover = None
else:
unit = self.text_units[i]
if unit.unit_type == 3:
self.lines.append(current_units)
current_units = []
current_width = 0
i += 1
continue
if unit.unit_type == 4:
if current_units:
self.lines.append(current_units)
i += 1
self.lines.append([unit])
current_units = []
current_width = 0
i += 1
continue
if current_width + unit.width > self.width:
if not current_units or self.one_line or (self.max_lines is not None and len(self.lines) == self.max_lines - 1):
# We need to split the current unit.
k = len(unit.string)-1
while k and current_width + unit.font.width(unit.string[:k] + self.ellipsis) > self.width:
k -= 1
ellipsis = self.ellipsis
if not k and self.ellipsis:
ellipsis = ""
if not k and (self.one_line or (self.max_lines is not None and len(self.lines) == self.max_lines - 1)):
added_ellipsis = False
while len(current_units) and sum([unit.width for unit in current_units]) + unit.font.width(self.ellipsis) > self.width:
this_unit = current_units[-1]
current_units = current_units[:-1]
current_width = sum([unit.width for unit in current_units])
k = len(this_unit.string)-1
while k and current_width + unit.font.width(this_unit.string[:k] + self.ellipsis) > self.width:
k -= 1
if k:
current_units.append(TextUnit(this_unit.string[:k] + self.ellipsis,this_unit.unit_type,this_unit.font))
added_ellipsis = True
break
if not added_ellipsis:
current_units.append(TextUnit(self.ellipsis,0,unit.font))
else:
current_units.append(TextUnit(unit.string[:k]+ellipsis,unit.unit_type,unit.font))
leftover = TextUnit(unit.string[k:],unit.unit_type,unit.font)
self.lines.append(current_units)
current_units = []
current_width = 0
if self.one_line or (self.max_lines is not None and len(self.lines) == self.max_lines):
return
else:
self.lines.append(current_units)
current_units = []
current_width = 0
if unit.unit_type == 1:
i += 1
else:
current_units.append(unit)
current_width += unit.width
i += 1
if current_units:
self.lines.append(current_units)
def units_from_text(self, text, font=None, whitespace=True):
if not font:
font = self.font
def char_width(char):
if _emoji_available and ord(char) in _emoji_values:
return 2
x = unicodedata.east_asian_width(char)
if x == 'Na': return 1 # Narrow
if x == 'N': return 1 # Narrow
if x == 'A': return 1 # Ambiguous
if x == 'W': return 2 # Wide
if x == 'F': return 1 # Fullwidth (treat as normal)
if x == 'H': return 1 # Halfwidth
print(f"Don't know how wide {x} is, assuming 1")
return 1
def classify(char):
if char == '\n': return 3 # break on line feed
if unicodedata.category(char) == 'Zs': return 1 # break on space
if char_width(char) > 1: return 2 # allow break on CJK characters (TODO: only really valid for Chinese and Japanese; Korean doesn't work this way
if self.break_all: return 2
return 0
units = []
offset = 0
current_unit = ""
while offset < len(text):
c = text[offset]
if not whitespace and c.isspace():
if current_unit:
units.append(TextUnit(current_unit,0,font))
current_unit = ""
units.append(TextUnit(' ',1,font))
offset += 1
continue
x = classify(c)
if x == 0:
current_unit += c
offset += 1
else:
if not current_unit:
units.append(TextUnit(c,x,font))
offset += 1
else:
units.append(TextUnit(current_unit,0,font))
current_unit = ""
if current_unit:
units.append(TextUnit(current_unit,0,font))
return units
def set_one_line(self, one_line=True):
self.one_line = one_line
self.reflow()
def set_ellipsis(self, ellipsis=""):
self.ellipsis = ellipsis
self.reflow()
def set_text(self, text):
self.text = text
self.text_units = self.units_from_text(text)
self.reflow()
def set_richtext(self, text, html=False):
f = self.font
self.text = text
tr = self
class RichTextParser(HTMLParser):
def __init__(self, html=False):
super(RichTextParser,self).__init__()
self.font_stack = []
self.tag_stack = []
self.current_font = f
self.units = []
self.link_stack = []
self.current_link = None
self.tag_group = None
self.is_html = html
self.whitespace_sensitive = not html
self.autoclose = ['br','meta','input']
self.title = ''
if self.is_html:
self.autoclose.extend(['img','link'])
self.surface_cache = {}
def handle_starttag(self, tag, attrs):
def make_bold(n):
if n == 0: return 1
if n == 2: return 3
if n == 4: return 5
if n == 6: return 7
return n
def make_italic(n):
if n == 0: return 2
if n == 1: return 3
if n == 4: return 6
if n == 5: return 7
return n
def make_monospace(n):
if n == 0: return 4
if n == 1: return 5
if n == 2: return 6
if n == 3: return 7
return n
if tag not in self.autoclose:
self.tag_stack.append(tag)
if tag in ['p','div','h1','h2','h3','li','tr','pre'] and not self.whitespace_sensitive: # etc?
if self.units and self.units[-1].unit_type != 3:
self.units.append(TextUnit('\n',3,self.current_font))
if tag == "b":
self.font_stack.append(self.current_font)
self.current_font = toaru_fonts.Font(make_bold(self.current_font.font_number),self.current_font.font_size,self.current_font.font_color)
elif tag == "i":
self.font_stack.append(self.current_font)
self.current_font = toaru_fonts.Font(make_italic(self.current_font.font_number),self.current_font.font_size,self.current_font.font_color)
elif tag == "color":
self.font_stack.append(self.current_font)
self.current_font = toaru_fonts.Font(self.current_font.font_number,self.current_font.font_size,int(attrs[0][0],16) | 0xFF000000)
elif tag == "mono":
self.font_stack.append(self.current_font)
self.current_font = toaru_fonts.Font(make_monospace(self.current_font.font_number),self.current_font.font_size,self.current_font.font_color)
elif tag == "pre":
self.font_stack.append(self.current_font)
self.current_font = toaru_fonts.Font(make_monospace(self.current_font.font_number),self.current_font.font_size,self.current_font.font_color)
elif tag == "link" and not self.is_html:
target = None
for attr in attrs:
if attr[0] == "target":
target = attr[1]
self.tag_group = []
self.link_stack.append(self.current_link)
self.current_link = target
self.font_stack.append(self.current_font)
self.current_font = toaru_fonts.Font(self.current_font.font_number,self.current_font.font_size,0xFF0000FF)
elif tag == "a":
target = None
for attr in attrs:
if attr[0] == "href":
target = attr[1]
self.tag_group = []
self.link_stack.append(self.current_link)
if target and self.is_html and not target.startswith('http:') and not target.startswith('https:'):
# This is actually more complicated than this check - protocol-relative stuff can work without full URLs
if target.startswith('/'):
base = urlparse(tr.base_dir)
target = f"{base.scheme}://{base.netloc}{target}"
else:
target = tr.base_dir + target
self.current_link = target
self.font_stack.append(self.current_font)
self.current_font = toaru_fonts.Font(self.current_font.font_number,self.current_font.font_size,0xFF0000FF)
elif tag == "h1":
self.font_stack.append(self.current_font)
self.current_font = toaru_fonts.Font(make_bold(self.current_font.font_number),20)
elif tag == "h2":
self.font_stack.append(self.current_font)
self.current_font = toaru_fonts.Font(make_bold(self.current_font.font_number),18)
elif tag == "h3":
self.font_stack.append(self.current_font)
self.current_font = toaru_fonts.Font(make_bold(self.current_font.font_number),16)
elif tag == "img":
self.handle_img(tag,attrs)
elif tag == "br":
units = tr.units_from_text('\n', self.current_font)
self.units.extend(units)
else:
pass
def handle_startendtag(self, tag, attrs):
if tag == "img":
self.handle_img(tag,attrs)
elif tag == "br":
units = tr.units_from_text('\n', self.current_font)
self.units.extend(units)
elif tag in ['p','div','h1','h2','h3','tr','pre'] and not self.whitespace_sensitive: # etc?
units = tr.units_from_text('\n', self.current_font)
self.units.extend(units)
else:
# Unknown start/end tag.
pass
def handle_endtag(self, tag):
if not self.tag_stack:
print(f"No stack when trying to close {tag}?")
if self.tag_stack[-1] != tag:
print(f"unclosed tag {self.tag_stack[-1]} when closing tag {tag}")
else:
self.tag_stack.pop()
if tag in ["b","i","color","mono","link","h1","h2","h3","a","pre"]:
self.current_font = self.font_stack.pop()
if tag in ['p','div','h1','h2','h3','li','tr','pre'] and not self.whitespace_sensitive: # etc?
units = tr.units_from_text('\n', self.current_font)
self.units.extend(units)
if tag in ["link","a"]:
self.current_link = self.link_stack.pop()
self.tag_group = None
def handle_data(self, data):
if 'title' in self.tag_stack:
self.title += data
if 'head' in self.tag_stack or 'script' in self.tag_stack: return
if 'pre' in self.tag_stack:
units = tr.units_from_text(data, self.current_font, whitespace=True)
else:
units = tr.units_from_text(data, self.current_font, whitespace=self.whitespace_sensitive)
if self.current_link:
for u in units:
u.set_extra('link',self.current_link)
if self.tag_group is not None:
for u in units:
u.set_tag_group(self.tag_group)
self.units.extend(units)
def handle_img(self, tag, attrs):
target = None
for attr in attrs:
if attr[0] == "src":
target = attr[1]
if target and self.is_html and not target.startswith('http:') and not target.startswith('https:'):
# This is actually more complicated than this check - protocol-relative stuff can work without full URLs
if target.startswith('/'):
base = urlparse(tr.base_dir)
target = f"{base.scheme}://{base.netloc}{target}"
else:
target = tr.base_dir + target
else:
if target and not self.is_html and not target.startswith('/'):
target = tr.base_dir + target
if target and self.is_html and not target.startswith('http:'):
target = tr.base_dir + target
if target and target.startswith('http:'):
x = hashlib.sha512(target.encode('utf-8')).hexdigest()
p = f'/tmp/.browser-cache.{x}'
if not os.path.exists(p):
try:
subprocess.check_output(['fetch','-o',p,target])
except:
print(f"Failed to download image: {target}")
pass
target = p
if target and os.path.exists(target):
try:
img = self.img_from_path(target)
except:
print(f"Failed to load image {target}, going to show backup image.")
img = self.img_from_path('/usr/share/icons/16/missing.png')
chop = math.ceil(img.get_height() / tr.line_height)
group = []
for i in range(chop):
u = TextUnit("",4,self.current_font)
u.set_extra('img',img)
u.set_extra('offset',i * tr.line_height)
if self.current_link:
u.set_extra('link',self.current_link)
u.set_tag_group(group)
u.width = img.get_width()
self.units.append(u)
def fix_whitespace(self):
out_units = []
last_was_whitespace = False
for unit in self.units:
if unit.unit_type == 3:
last_was_whitespace = True
out_units.append(unit)
elif unit.unit_type == 1 and unit.string == ' ':
if last_was_whitespace:
continue
last_was_whitespace = True
out_units.append(unit)
else:
last_was_whitespace = False
out_units.append(unit)
self.units = out_units
def img_from_path(self, path):
if not path in self.surface_cache:
s = cairo.ImageSurface.create_from_png(path)
self.surface_cache[path] = s
return s
else:
return self.surface_cache[path]
parser = RichTextParser(html=html)
parser.feed(text)
self.title = parser.title
if html:
parser.fix_whitespace()
self.text_units = parser.units
self.reflow()
def set_font(self, new_font):
self.font = new_font
self.line_height = self.font.font_size
self.reflow()
def set_line_height(self, new_line_height):
self.line_height = new_line_height
self.reflow()
def resize(self, new_width, new_height):
needs_reflow = self.width != new_width
self.width = new_width
self.height = new_height
if needs_reflow:
self.reflow()
def move(self, new_x, new_y):
self.x = new_x
self.y = new_y
def get_offset_at_index(self, index):
""" Only works for one-liners... """
if not self.lines:
return None, (0, 0, 0, 0)
left_align = 0
xline = self.lines[0]
if self.align == 1: # right align
left_align = self.width - sum([u.width for u in xline])
elif self.align == 2: # center
left_align = int((self.width - sum([u.width for u in xline])) / 2)
i = 0
for unit in xline:
if i == index:
return unit, (0, left_align, left_align, i)
left_align += unit.width
i += 1
return None, (0, left_align, left_align, i)
def pick(self, x, y):
# Determine which line this click belongs in
if x < self.x or x > self.x + self.width or y < self.y or y > self.y + self.height:
return None, None
top_align = 0
if len(self.lines) < int(self.height / self.line_height):
if self.valign == 1: # bottom
top_align = self.height - len(self.lines) * self.line_height
elif self.valign == 2: # middle
top_align = int((self.height - len(self.lines) * self.line_height) / 2)
new_y = y - top_align - self.y - 2 # fuzz factor
line = int(new_y / self.line_height)
if line < len(self.lines[self.scroll:]):
left_align = 0
xline = self.lines[self.scroll+line]
if self.align == 1: # right align
left_align = self.width - sum([u.width for u in xline])
elif self.align == 2: # center
left_align = int((self.width - sum([u.width for u in xline])) / 2)
i = 0
for unit in xline:
if x >= self.x + left_align and x < self.x + left_align + unit.width:
return unit, (line, left_align, x - self.x, i)
left_align += unit.width
i += 1
return None, (line, left_align, x - self.x, i)
return None, None
def click(self, x, y):
unit, _ = self.pick(x,y)
return unit
def draw(self, context):
current_height = self.line_height
top_align = 0
if len(self.lines) < int(self.height / self.line_height):
if self.valign == 1: # bottom
top_align = self.height - len(self.lines) * self.line_height
elif self.valign == 2: # middle
top_align = int((self.height - len(self.lines) * self.line_height) / 2)
su = context.get_cairo_surface() if 'get_cairo_surface' in dir(context) else None
cr = cairo.Context(su) if su else None
for line in self.lines[self.scroll:]:
if current_height > self.height:
break
left_align = 0
if self.align == 1: # right align
left_align = self.width - sum([u.width for u in line])
elif self.align == 2: # center
left_align = int((self.width - sum([u.width for u in line])) / 2)
for unit in line:
if unit.unit_type == 4:
cr.save()
extra = 3
cr.translate(self.x + left_align, self.y + current_height + top_align)
if 'hilight' in unit.extra and unit.extra['hilight']:
cr.rectangle(0,-self.line_height+extra,unit.extra['img'].get_width(),self.line_height)
cr.set_source_rgb(1,0,0)
cr.fill()
cr.rectangle(0,-self.line_height+extra,unit.extra['img'].get_width(),self.line_height)
cr.set_source_surface(unit.extra['img'],0,-unit.extra['offset']-self.line_height+extra)
cr.fill()
cr.restore()
elif unit.unit_type == 2 and 'emoji' in unit.extra:
cr.save()
extra = 3
cr.translate(self.x + left_align, self.y + current_height + top_align -self.line_height+extra)
if unit.extra['img'].get_height() > self.line_height - 3:
scale = (self.line_height - 3) / unit.extra['img'].get_height()
cr.scale(scale,scale)
cr.rectangle(0,0,unit.extra['img'].get_width(),unit.extra['img'].get_height())
cr.set_source_surface(unit.extra['img'],0,0)
cr.fill()
cr.restore()
elif unit.font:
unit.font.write(context, self.x + left_align, self.y + current_height + top_align, unit.string)
left_align += unit.width
current_height += self.line_height

View File

@ -0,0 +1,92 @@
import ctypes
import importlib
_cairo_lib = None
_cairo_module = None
_cairo_module_lib = None
_lib = None
if not _lib:
_lib = ctypes.CDLL('libtoaru_ext_freetype_fonts.so')
#_lib.init_shmemfonts() # No init call in new library
_lib.freetype_draw_string_width.argtypes = [ctypes.c_char_p]
_lib.freetype_draw_string_width.restype = ctypes.c_uint32
_lib.freetype_font_name.restype = ctypes.c_char_p
_lib.freetype_draw_string.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_uint32, ctypes.c_char_p]
_lib.freetype_draw_string_shadow.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_uint32, ctypes.c_char_p, ctypes.c_uint32, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_double]
_lib.freetype_get_active_font_face.restype = ctypes.c_void_p
FONT_SANS_SERIF = 0
FONT_SANS_SERIF_BOLD = 1
FONT_SANS_SERIF_ITALIC = 2
FONT_SANS_SERIF_BOLD_ITALIC = 3
FONT_MONOSPACE = 4
FONT_MONOSPACE_BOLD = 5
FONT_MONOSPACE_ITALIC = 6
FONT_MONOSPACE_BOLD_ITALIC = 7
FONT_JAPANESE = 8
FONT_SYMBOLA = 9
def get_active_font():
return _lib.freetype_get_active_font_face()
def get_cairo_face():
global _cairo_lib
global _cairo_module
global _cairo_module_lib
if not _cairo_lib:
_cairo_lib = ctypes.CDLL('libcairo.so')
_cairo_module = importlib.import_module('_cairo')
_cairo_module_lib = ctypes.CDLL(_cairo_module.__file__)
cfffcfff = _cairo_lib.cairo_ft_font_face_create_for_ft_face
cfffcfff.argtypes = [ctypes.c_void_p, ctypes.c_int]
cfffcfff.restype = ctypes.c_void_p
ft_face = cfffcfff(get_active_font(),0)
pcfffff = _cairo_module_lib.PycairoFontFace_FromFontFace
pcfffff.argtypes = [ctypes.c_void_p]
pcfffff.restype = ctypes.py_object
return pcfffff(ft_face)
class Font(object):
def __init__(self, font_number, font_size=10, font_color=0xFF000000):
self.font_number = font_number
self.font_size = font_size
self.font_color = font_color
self.shadow = None
def set_shadow(self, shadow):
self.shadow = shadow
def _use(self):
_lib.freetype_set_font_face(self.font_number)
_lib.freetype_set_font_size(self.font_size)
def width(self, string):
self._use()
string = string.encode('utf-8')
return _lib.freetype_draw_string_width(string)
def write(self, ctx, x, y, string, shadow=None):
if self.shadow:
shadow = self.shadow
self._use()
foreground = self.font_color
string = string.encode('utf-8')
if hasattr(ctx,"_gfx"):
# Allow a yutani.Window to be passed to this instead of a real graphics context
ctx = ctx._gfx
if shadow:
color, darkness, offset_x, offset_y, radius = shadow
_lib.freetype_draw_string_shadow(ctx,x,y,foreground,string,color,darkness,offset_x,offset_y,radius)
else:
_lib.freetype_draw_string(ctx,x,y,foreground,string)
@property
def name(self):
return _lib.freetype_font_name(self.font_number).decode('utf-8')

View File

@ -12,6 +12,10 @@ yutani_gfx_lib = None
yutani_ctx = None
yutani_windows = {}
_cairo_lib = None
_cairo_module = None
_cairo_module_lib = None
_libc = CDLL('libc.so')
def usleep(microseconds):
@ -453,6 +457,9 @@ class GraphicsBuffer(object):
self._sprite = yutani_gfx_lib.create_sprite(width,height,2)
self._gfx = cast(yutani_gfx_lib.init_graphics_sprite(self._sprite),POINTER(Window._gfx_context_t))
def get_cairo_surface(self):
return Window.get_cairo_surface(self)
def get_value(self,x,y):
return cast(self._gfx.contents.backbuffer,POINTER(c_uint32))[self.width * y + x]
@ -504,6 +511,39 @@ class Window(object):
self.closed = False
def get_cairo_surface(self):
"""Obtain a pycairo.ImageSurface representing the window backbuffer."""
global _cairo_lib
global _cairo_module
global _cairo_module_lib
if not _cairo_lib:
_cairo_lib = CDLL('libcairo.so')
_cairo_module = importlib.import_module('_cairo')
_cairo_module_lib = CDLL(_cairo_module.__file__)
buffer = self._gfx.contents.backbuffer
width = self.width
height = self.height
format = _cairo_module.FORMAT_ARGB32
# int cairo_format_stride_for_width(cairo_format_t format, int width)
cfsfw = _cairo_lib.cairo_format_stride_for_width
cfsfw.argtypes = [c_int, c_int]
cfsfw.restype = c_int
# stride = cairo_format_stride_for_width(format, width)
stride = cfsfw(format, width)
# cairo_surface_t * cairo_image_surface_create_for_data(unsigned char * data, cairo_format_t format, int ...)
ciscfd = _cairo_lib.cairo_image_surface_create_for_data
ciscfd.argtypes = [POINTER(c_char), c_int, c_int, c_int, c_int]
ciscfd.restype = c_void_p
# surface = cairo_image_surface_create_for_data(buffer,format,width,height,stride)
surface = ciscfd(buffer,format,width,height,stride)
# PyObject * PycairoSurface_FromSurface(cairo_surface_t * surface, PyObject * base)
pcsfs = _cairo_module_lib.PycairoSurface_FromSurface
pcsfs.argtypes = [c_void_p, c_int]
pcsfs.restype = py_object
# return PycairoSurface_FromSurface(surface, NULL)
return pcsfs(surface, 0)
def set_title(self, title, icon=None):
"""Advertise this window with the given title and optional icon string."""
self.title = title

View File

@ -0,0 +1,62 @@
import yutani
import menu_bar
def handle_event(msg):
if msg.type == yutani.Message.MSG_SESSION_END:
msg.free()
return False
elif msg.type == yutani.Message.MSG_KEY_EVENT:
if msg.wid in yutani.yutani_windows:
yutani.yutani_windows[msg.wid].keyboard_event(msg)
elif msg.type == yutani.Message.MSG_WINDOW_FOCUS_CHANGE:
if msg.wid in yutani.yutani_windows:
window = yutani.yutani_windows[msg.wid]
if msg.wid in menu_bar.menu_windows:
if msg.focused == 0:
window.leave_menu()
if window.root and not window.root.menus and window.root.focused:
window.root.focused = 0
window.root.draw()
if False: pass
elif msg.focused == 0 and 'menus' in dir(window) and window.menus:
window.focused = 1
window.draw()
else:
if 'focus_changed' in dir(window):
window.focus_changed(msg)
window.focused = msg.focused
window.draw()
elif msg.type == yutani.Message.MSG_RESIZE_OFFER:
if msg.wid in yutani.yutani_windows:
yutani.yutani_windows[msg.wid].finish_resize(msg)
elif msg.type == yutani.Message.MSG_WINDOW_MOVE:
if msg.wid in yutani.yutani_windows:
window = yutani.yutani_windows[msg.wid]
if 'window_moved' in dir(window):
window.window_moved(msg)
else:
window.x = msg.x
window.y = msg.y
elif msg.type == yutani.Message.MSG_WINDOW_MOUSE_EVENT:
if msg.wid in yutani.yutani_windows:
window = yutani.yutani_windows[msg.wid]
if msg.wid in menu_bar.menu_windows:
if window.root:
if msg.new_x >= 0 and msg.new_x < window.width and msg.new_y >= 0 and msg.new_y < window.height:
window.root.hovered_menu = window
else:
window.root.hovered_menu = None
window.mouse_action(msg)
if False: pass
elif 'mouse_event' in dir(window):
window.mouse_event(msg)
msg.free()
return True
def mainloop():
status = True
while status:
# Poll for events.
msg = yutani.yutani_ctx.poll()
status = handle_event(msg)