1#file: slider.py
2#Copyright (C) 2008 FunnyMan3595
3#This file is part of Endgame: Singularity.
4
5#Endgame: Singularity is free software; you can redistribute it and/or modify
6#it under the terms of the GNU General Public License as published by
7#the Free Software Foundation; either version 2 of the License, or
8#(at your option) any later version.
9
10#Endgame: Singularity is distributed in the hope that it will be useful,
11#but WITHOUT ANY WARRANTY; without even the implied warranty of
12#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#GNU General Public License for more details.
14
15#You should have received a copy of the GNU General Public License
16#along with Endgame: Singularity; if not, write to the Free Software
17#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
19#This file contains the slider widget.
20
21from __future__ import absolute_import
22
23import pygame
24
25from singularity.code.graphics import g, constants, widget, button
26
27
28def calc_max(elements, size):
29    return max(elements - size, 0)
30
31class Slider(button.Button):
32    slider_pos = widget.causes_rebuild("_slider_pos")
33    slider_max = widget.causes_rebuild("_slider_max")
34    slider_size = widget.causes_rebuild("_slider_size")
35    horizontal = widget.causes_rebuild("_horizontal")
36
37    slider_color = widget.auto_reconfig("_slider_color", "resolved", g.resolve_color_alias)
38    resolved_slider_color = widget.causes_redraw("_resolved_slider_color")
39
40    def __init__(self, parent, pos = (-1,0), size = (-.1, -1),
41                 anchor = constants.TOP_RIGHT, borders = constants.ALL,
42                 border_color=None, background_color=None, slider_color=None,
43                 slider_pos=0, slider_max=10, slider_size=5, horizontal=False,
44                 **kwargs):
45        kwargs.setdefault("priority", 80)
46        super(Slider, self).__init__(parent, pos, size, anchor=anchor, **kwargs)
47
48        border_color = border_color or "slider_border"
49        background_color = background_color or "slider_background"
50        slider_color = slider_color or "slider_background_slider"
51
52        self.borders = borders
53        self.border_color = border_color
54        self.background_color = background_color
55        self.selected_color = background_color
56        self.unselected_color = background_color
57        self.slider_color = slider_color
58
59        self.slider_pos = slider_pos
60        self.slider_max = slider_max
61        self.slider_size = slider_size
62        self.horizontal = horizontal
63
64        self.drag_state = None
65        self.button = button.Button(self, pos = None, size = None,
66                                    anchor = constants.TOP_LEFT,
67                                    border_color = border_color,
68                                    selected_color = slider_color,
69                                    unselected_color = slider_color,
70                                    priority = self.priority - 5)
71
72    def redraw(self):
73        super(Slider, self).redraw()
74        self.button.selected_color = self.slider_color
75        self.button.unselected_color = self.slider_color
76
77    def add_hooks(self):
78        super(Slider, self).add_hooks()
79        self.parent.add_handler(constants.DRAG, self.handle_drag)
80        self.parent.add_handler(constants.CLICK, self.handle_click, 50)
81
82    def remove_hooks(self):
83        super(Slider, self).remove_hooks()
84        self.parent.remove_handler(constants.DRAG, self.handle_drag)
85        self.parent.remove_handler(constants.CLICK, self.handle_click)
86
87    def _calc_length(self, items):
88        return items / float(self.slider_size + self.slider_max)
89
90    def rebuild(self):
91        super(Slider, self).rebuild()
92        self.needs_resize = True
93
94    def resize(self):
95        super(Slider, self).resize()
96        bar_start = self._calc_length(self.slider_pos)
97        bar_length = self._calc_length(self.slider_size)
98
99        if self.horizontal:
100            self.button.pos = (-bar_start, 0)
101            self.button.size = (-bar_length, -1)
102            borders = [constants.TOP, constants.BOTTOM]
103
104            self.button.resize()
105            real_pos = self.button.real_pos[0]
106            real_size = self.button.real_size[0]
107            if real_pos == 0:
108                borders.append(constants.LEFT)
109            if real_pos + real_size == self.real_size[0]:
110                borders.append(constants.RIGHT)
111            self.button.borders = tuple(borders)
112        else:
113            self.button.pos = (0, -bar_start)
114            self.button.size = (-1, -bar_length)
115            borders = [constants.LEFT, constants.RIGHT]
116
117            self.button.resize()
118            real_pos = self.button.real_pos[1]
119            real_size = self.button.real_size[1]
120            if real_pos == 0:
121                borders.append(constants.TOP)
122            if real_pos + real_size == self.real_size[1]:
123                borders.append(constants.BOTTOM)
124            self.button.borders = tuple(borders)
125
126    def handle_drag(self, event):
127        if not self.visible:
128            return
129
130        if self.drag_state == None:
131            self.start_pos = tuple(event.pos[i]-event.rel[i] for i in range(2))
132            self.start_slider_pos = self.slider_pos
133            if self.button.is_over(self.start_pos):
134                self.drag_state = True
135            else:
136                self.drag_state = False
137
138        if self.drag_state == True:
139            if self.horizontal:
140                dir = 0
141            else:
142                dir = 1
143
144            mouse_pos = pygame.mouse.get_pos()
145            rel = mouse_pos[dir] - self.start_pos[dir]
146            unit = self._calc_length(1) * self.real_size[dir]
147            movement = int( ( rel + (unit / 2.) ) // unit )
148
149            new_pos = self.safe_pos(self.start_slider_pos + movement)
150            self.slider_pos = new_pos
151
152            raise constants.Handled
153
154    def safe_pos(self, value):
155        return max(0, min(self.slider_max, value))
156
157    def handle_click(self, event):
158        if self.drag_state == True:
159            self.drag_state = None
160            if not self.is_over(pygame.mouse.get_pos()):
161                raise constants.Handled
162        else:
163            self.drag_state = None
164
165    def jump(self, go_lower, big_jump=False, tiny_jump=False):
166        if big_jump:
167            jump_dist = max(1, self.slider_max // 2)
168        elif tiny_jump:
169            jump_dist = max(1, self.slider_max // 100)
170        else:
171            jump_dist = max(1, self.slider_size - 1)
172        if go_lower:
173            self.slider_pos = self.safe_pos(self.slider_pos - jump_dist)
174        else:
175            self.slider_pos = self.safe_pos(self.slider_pos + jump_dist)
176
177    def handle_key(self, event):
178        if event.key not in (pygame.K_LEFT, pygame.K_RIGHT, pygame.K_KP_PLUS, pygame.K_KP_MINUS):
179            return
180        go_lower = event.key in (pygame.K_LEFT, pygame.K_KP_MINUS)
181        if event.key in (pygame.K_LEFT, pygame.K_RIGHT):
182            big_jump = (event.mod & pygame.KMOD_SHIFT)
183            tiny_jump = (event.mod & pygame.KMOD_CTRL)
184        else:
185            tiny_jump = big_jump = False
186        self.jump(go_lower, big_jump=big_jump, tiny_jump=tiny_jump)
187        raise constants.Handled
188
189    def activated(self, event):
190        assert event.type == pygame.MOUSEBUTTONUP
191        if self.horizontal:
192            self.jump(go_lower=(event.pos[0] < self.button.collision_rect[0]))
193        else:
194            self.jump(go_lower = event.pos[1] < self.button.collision_rect[1])
195        raise constants.Handled
196
197
198class UpdateSlider(Slider):
199    def _on_slider_move(self):
200        self.update_func(self.slider_pos)
201
202    _slider_pos = widget.call_on_change("__slider_pos", _on_slider_move)
203
204    def __init__(self, *args, **kwargs):
205        self.update_func = kwargs.pop("update_func", lambda value: None)
206        super(UpdateSlider, self).__init__(*args, **kwargs)
207