1#!/usr/bin/env python3
2
3"""
4Widgets that don't really fit into any of the other categories.
5"""
6
7import pyglet
8import autoprop
9
10from vecrec import Vector, Rect
11from glooey import drawing
12from glooey.widget import Widget
13from glooey.images import Background
14from glooey.text import Label
15from glooey.helpers import *
16
17@autoprop
18class Spacer(Widget):
19
20    def __init__(self, min_width=0, min_height=0):
21        super().__init__()
22        self._min_width = min_width
23        self._min_height = min_height
24
25    def do_claim(self):
26        return self._min_width, self._min_height
27
28
29@autoprop
30class Placeholder(Widget):
31    custom_color = 'green'
32    custom_alignment = 'fill'
33
34    def __init__(self, min_width=0, min_height=0, color=None, align=None):
35        super().__init__()
36        self._color = color or self.custom_color
37        self._min_width = min_width
38        self._min_height = min_height
39        self.vertex_list = None
40        self.alignment = align or self.custom_alignment
41
42    def do_claim(self):
43        return self._min_width, self._min_height
44
45    def do_regroup(self):
46        if self.vertex_list is not None:
47            self.batch.migrate(
48                    self.vertex_list, pyglet.gl.GL_LINES,
49                    self.group, self.batch)
50
51    def do_draw(self):
52        if self.vertex_list is None:
53            self.vertex_list = self.batch.add(
54                    12, pyglet.gl.GL_LINES, self.group, 'v2f', 'c4B')
55
56        # Shrink the rectangle by half-a-pixel so there's no ambiguity about
57        # where the line should be drawn.  (The problem is that the widget rect
58        # is always rounded to the nearest pixel, but OpenGL doesn't seem
59        # deterministic about which side of the pixel it draws the line on.)
60        rect = self.rect.get_shrunk(0.5)
61        top_left = rect.top_left
62        top_right = rect.top_right
63        bottom_left = rect.bottom_left
64        bottom_right = rect.bottom_right
65
66        # Originally I used GL_LINE_STRIP, but I couldn't figure out how to
67        # stop the place holders from connecting to each other (i.e. I couldn't
68        # figure out how to break the line strip).  Now I'm just using GL_LINES
69        # instead.
70
71        self.vertex_list.vertices = (
72                # The outline.  Add one to the bottom right coordinate.  I
73                # don't know why this is necessary, but without it the pixel in
74                # the bottom-right corner doesn't get filled in.
75                bottom_left.tuple + (bottom_right + (1,0)).tuple +
76                bottom_right.tuple + top_right.tuple +
77                top_right.tuple + top_left.tuple +
78                top_left.tuple + bottom_left.tuple +
79
80                # The cross
81                bottom_left.tuple + top_right.tuple +
82                bottom_right.tuple + top_left.tuple
83        )
84        color = drawing.Color.from_anything(self.color)
85        self.vertex_list.colors = 12 * color.tuple
86
87    def do_undraw(self):
88        if self.vertex_list is not None:
89            self.vertex_list.delete()
90            self.vertex_list = None
91
92    def get_color(self):
93        return self._color
94
95    def set_color(self, new_color):
96        self._color = new_color
97        self._draw()
98
99    def get_min_width(self):
100        return self._min_width
101
102    def set_min_width(self, new_width):
103        self._min_width = new_width
104        self._repack()
105
106    def get_min_height(self):
107        return self._min_height
108
109    def set_min_height(self, new_height):
110        self._min_height = new_height
111        self._repack()
112
113
114@autoprop
115class EventLogger(Placeholder):
116
117    def on_click(self, widget):
118        print(f'{self}.on_click(widget={widget})')
119
120    def on_double_click(self, widget):
121        print(f'{self}.on_double_click(widget={widget})')
122
123    def on_rollover(self, widget, new_state, old_state):
124        print(f'{self}.on_rollover(new_state={new_state}, old_state={old_state})')
125
126    def on_mouse_press(self, x, y, button, modifiers):
127        super().on_mouse_press(x, y, button, modifiers)
128        print(f'{self}.on_mouse_press(x={x}, y={y}, button={button}, modifiers={modifiers})')
129
130    def on_mouse_release(self, x, y, button, modifiers):
131        super().on_mouse_release(x, y, button, modifiers)
132        print(f'{self}.on_mouse_release(x={x}, y={y}, button={button}, modifiers={modifiers})')
133
134    def on_mouse_hold(self, dt):
135        print(f'{self}.on_mouse_hold(dt={dt})')
136
137    def on_mouse_motion(self, x, y, dx, dy):
138        super().on_mouse_motion(x, y, dx, dy)
139        print(f'{self}.on_mouse_motion(x={x}, y={y}, dx={dx}, dy={dy})')
140
141    def on_mouse_enter(self, x, y):
142        super().on_mouse_enter(x, y)
143        print(f'{self}.on_mouse_enter(x={x}, y={y})')
144
145    def on_mouse_leave(self, x, y):
146        super().on_mouse_leave(x, y)
147        print(f'{self}.on_mouse_leave(x={x}, y={y})')
148
149    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
150        super().on_mouse_drag(x, y, dx, dy, buttons, modifiers)
151        print(f'{self}.on_mouse_drag(x={x}, y={y}, dx={dx}, dy={dy}, buttons={buttons}, modifiers={modifiers})')
152
153    def on_mouse_drag_enter(self, x, y):
154        super().on_mouse_drag_enter(x, y)
155        print(f'{self}.on_mouse_drag_enter(x={x}, y={y})')
156
157    def on_mouse_drag_leave(self, x, y):
158        super().on_mouse_drag_leave(x, y)
159        print(f'{self}.on_mouse_drag_leave(x={x}, y={y})')
160
161    def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
162        super().on_mouse_scroll(x, y, scroll_x, scroll_y)
163        print(f'{self}.on_mouse_scroll(x={x}, y={y}, scroll_x={scroll_x}, scroll_y={scroll_y})')
164
165
166@autoprop
167class LoremIpsum(Label):
168
169    def __init__(self, num_sentences=None, num_paragraphs=None, line_wrap=300, **style):
170        text = drawing.lorem_ipsum(num_sentences, num_paragraphs)
171        super().__init__(text, line_wrap=line_wrap, **style)
172
173
174@autoprop
175class FillBar(Widget):
176    Base = Background
177    Fill = Background
178
179    def __init__(self, fraction_filled=0):
180        super().__init__()
181
182        self._base = self.Base()
183        self._fill = self.Fill()
184        self._fill_fraction = fraction_filled
185        self._fill_group = None
186
187        self._attach_child(self._base)
188        self._attach_child(self._fill)
189
190    def do_claim(self):
191        min_width = max(self.base.claimed_width, self.fill.claimed_width)
192        min_height = max(self.base.claimed_height, self.fill.claimed_height)
193        return min_width, min_height
194
195    def do_resize(self):
196        self._update_fill()
197
198    def do_regroup_children(self):
199        base_layer = pyglet.graphics.OrderedGroup(1, self.group)
200        fill_layer = pyglet.graphics.OrderedGroup(2, self.group)
201
202        self._fill_group = drawing.ScissorGroup(parent=fill_layer)
203        self._update_fill()
204
205        self._base._regroup(base_layer)
206        self._fill._regroup(self._fill_group)
207
208    def do_draw(self):
209        self._update_fill()
210
211    def get_fill(self):
212        return self._fill
213
214    def get_base(self):
215        return self._base
216
217    def get_fraction_filled(self):
218        return self._fill_fraction
219
220    def set_fraction_filled(self, new_fraction):
221        self._fill_fraction = new_fraction
222        self._update_fill()
223
224    def _update_fill(self):
225        if self._fill_group and self.fill.rect:
226            self._fill_group.rect = self.fill.rect.copy()
227            self._fill_group.rect.width *= self._fill_fraction
228
229
230