1# Copyright 2004-2021 Tom Rothamel <pytom@bishoujo.us>
2#
3# Permission is hereby granted, free of charge, to any person
4# obtaining a copy of this software and associated documentation files
5# (the "Software"), to deal in the Software without restriction,
6# including without limitation the rights to use, copy, modify, merge,
7# publish, distribute, sublicense, and/or sell copies of the Software,
8# and to permit persons to whom the Software is furnished to do so,
9# subject to the following conditions:
10#
11# The above copyright notice and this permission notice shall be
12# included in all copies or substantial portions of the Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22init python:
23
24    # Styles
25    style.prefs_frame = Style(style.default, help="the frame containing all of the preferences")
26    style.prefs_frame_box = Style(style.default, help="the box inside the frame containing all of the preferences")
27
28    style.prefs_pref_frame = Style(style.menu_frame, help="a frame containing a single preference")
29    style.prefs_pref_box = Style(style.vbox, help="the box separating the preference label from the preference choices")
30    style.prefs_pref_choicebox = Style(style.vbox, help="the box containing the preference choices")
31
32    style.prefs_label = Style(style.label, help="a preference label (window)")
33    style.prefs_label_text = Style(style.label_text, help="a preference label (text)")
34
35    style.prefs_button = Style(style.radio_button, help="preference value button")
36    style.prefs_button_text = Style(style.radio_button_text, help="preference value button (text)")
37
38    style.prefs_slider = Style(style.slider, help="preference value slider bar")
39
40    style.prefs_volume_box = Style(style.vbox, help="box containing a volume slider and soundtest button")
41    style.prefs_volume_slider = Style(style.prefs_slider, help="volume slider bar")
42
43    style.soundtest_button = Style(style.small_button, help="soundtest button")
44    style.soundtest_button_text = Style(style.small_button_text, help="soundtest button (text)")
45
46    style.prefs_jump = Style(style.prefs_pref_frame, help="jump preference frame")
47    style.prefs_jump_button = Style(style.button, help="jump preference button")
48    style.prefs_jump_button_text = Style(style.button_text, help="jump preference button (text)")
49
50    # This is a map from the name of the style that is applied to
51    # a list of preferences that should be placed into a vbox
52    # with that style.
53    config.preferences = { }
54
55    # This is a map from preference name to that preference
56    # object, that can be used in rearranging preferences.
57    config.all_preferences = { }
58
59    # Should the soundtest button be before the volume slider?
60    config.soundtest_before_volume = False
61
62    def _prefs_screen_run(prefs_map):
63
64        layout.navigation("preferences")
65
66        ui.window(style='prefs_frame')
67        ui.fixed(style='prefs_frame_box')
68
69        for style, prefs in prefs_map.iteritems():
70
71            ui.vbox(style=style)
72            for i in prefs:
73                i.render()
74            ui.close()
75
76        ui.close()
77
78        ui.interact(mouse="gamemenu")
79
80    class _Preference(object):
81        """
82        This is a class that's used to represent a multiple-choice
83        preference.
84        """
85
86        def __init__(self, name, field, values, base=_preferences):
87            """
88            @param name: The name of this preference. It will be
89            displayed to the user.
90
91            @param variable: The field on the base object
92            that will be assigned the selected value. This field
93            must exist.
94
95            @param values: A list of value name, value, condition
96            triples. The value name is the name of this value that
97            will be shown to the user. The value is the literal
98            python value that will be assigned if this value is
99            selected. The condition is a condition that will be
100            evaluated to determine if this is a legal value. If no
101            conditions are true, this preference will not be
102            displayed to the user. A condition of None is always
103            considered to be True.
104
105            @param base: The base object on which the variable is
106               read from and set. This defaults to _preferences,
107               the user preferences object.
108            """
109
110            self.name = name
111            self.field = field
112            self.values = values
113            self.base = base
114
115            config.all_preferences[name] = self
116
117
118        def render(self):
119            values = [ (name, val) for name, val, cond in self.values
120                       if cond is None or renpy.eval(cond) ]
121
122            if not values:
123                return
124
125            ui.window(style=style.prefs_pref_frame[self.name])
126            ui.vbox(style=style.prefs_pref_box[self.name])
127
128            layout.label(self.name, "prefs")
129
130            cur = getattr(self.base, self.field)
131
132            ui.hbox(style=style.prefs_pref_choicebox[self.name])
133
134            for name, value in values:
135                def clicked(value=value):
136                    setattr(self.base, self.field, value)
137                    return True
138
139                layout.button(name, "prefs",
140                              selected=(cur==value),
141                              clicked=clicked)
142
143            ui.close() # choicebox
144            ui.close() # vbox
145
146    class _VolumePreference(object):
147        """
148        This represents a preference that controls one of the
149        volumes in the system. It is represented as a slider bar,
150        and a button that can be pushed to play a sample sound on
151        a channel.
152        """
153
154        def __init__(self, name, mixer, enable='True', sound='None', channel="sound"):
155            """
156            @param name: The name of this preference, as shown to the user.
157
158            @param mixer: The mixer this preference controls.
159
160            @param enable: A string giving a python expression. If
161            the expression is evaluates to false, this preference
162            is not shown.
163
164            @param sound: A string that is evaluated to yield
165            another string. The yielded string is expected to give
166            a sound file, which is played as the sample sound. (We
167            apologize for the convolution of this.)
168
169            @param channel: The number of the channel the sample
170            sound is played on.
171            """
172
173            self.name = name
174            self.mixer = mixer
175            self.enable = enable
176            self.sound = sound
177            self.channel = channel
178
179            config.all_preferences[name] = self
180
181        def render(self):
182
183            if not eval(self.enable):
184                return
185
186            sound = eval(self.sound)
187
188            ui.window(style=style.prefs_pref_frame[self.name])
189            ui.vbox(style=style.prefs_pref_box[self.name])
190
191            layout.label(self.name, "prefs")
192
193            ui.vbox(style=style.prefs_volume_box[self.name])
194
195            if sound:
196                def clicked():
197                    renpy.sound.play(sound, channel=self.channel)
198
199            if sound and config.soundtest_before_volume:
200                layout.button(u"Test", "soundtest", clicked=clicked, index=self.name)
201
202            def changed(v):
203                _preferences.set_volume(self.mixer, v / 128.0)
204
205            ui.bar(128,
206                   int(_preferences.get_volume(self.mixer) * 128),
207                   changed=changed,
208                   style=style.prefs_volume_slider[self.name])
209
210            if sound and not config.soundtest_before_volume:
211                layout.button(u"Test", "soundtest", clicked=clicked, index=self.name)
212
213            ui.close()
214            ui.close()
215
216    class _SliderPreference(object):
217        """
218        A class that represents a preference that is controlled by a
219        slider.
220        """
221
222        def __init__(self, name, field, range, enable='True', base=_preferences):
223            """
224            @param name: The name of this preference, that is shown to the user.
225
226            @param range: An integer giving the maximum value of
227            this slider. The slider goes from 0 to range.
228
229            @param get: A function that's called to get the
230            initial value of the slider. It's called with no
231            arguments, and should return an integet between 0 and
232            range, inclusive.
233
234            @param set: A function that's called when the value of
235            the slider is set by the user. It is called with a
236            single integer, in the range 0 to range, inclusive.
237
238            @param enable: A string giving a python expression. If
239            the expression is evaluates to false, this preference
240            is not shown.
241            """
242
243            self.name = name
244            self.field = field
245            self.range = range
246            self.enable = enable
247            self.base = base
248
249            config.all_preferences[name] = self
250
251        def get(self):
252            value = getattr(self.base, self.field)
253            if value == 0:
254                value = self.range
255            else:
256                value -= 1
257
258            return value
259
260        def set(self, value):
261            value += 1
262            if value == self.range + 1:
263                value = 0
264
265            setattr(self.base, self.field, value)
266
267        def render(self):
268
269            if not eval(self.enable):
270                return
271
272            ui.window(style=style.prefs_pref_frame[self.name])
273            ui.vbox(style=style.prefs_pref_box[self.name])
274
275            layout.label(self.name, "prefs")
276
277            def changed(v):
278                self.set(v)
279
280            ui.bar(self.range,
281                   self.get(),
282                   changed=changed,
283                   style=style.prefs_slider[self.name])
284
285            ui.close()
286
287
288    class _JumpPreference(object):
289
290        def __init__(self, name, target, condition="True", show="True"):
291            self.name = name
292            self.target = target
293            self.condition = condition
294            self.show = show
295
296            config.all_preferences[name] = self
297
298        def render(self):
299
300            if not eval(self.show):
301                return
302
303            ui.window(style=style.prefs_jump[self.name])
304
305            if eval(self.condition):
306                clicked=ui.jumps(self.target)
307            else:
308                clicked=None
309
310            layout.button(self.name, 'prefs_jump', clicked=clicked)
311
312    def _remove_preference(name):
313        """
314        Removes the preference with the given name from the
315        preferences menu.
316        """
317
318        pref = config.all_preferences.get(name, None)
319        if not pref:
320            return
321
322        for k, v in config.preferences.iteritems():
323            if pref in v:
324                v.remove(pref)
325
326init python hide:
327
328    # Enablers for some preferences.
329    config.sample_sound = None
330    config.sample_voice = None
331    config.has_transitions = True
332    config.has_cps = True
333    config.has_afm = True
334    config.has_skipping = True
335    config.has_skip_after_choice = True
336    config.always_has_joystick = False
337    config.has_joystick = True
338
339
340    # Left
341
342    _Preference(u'Display', 'fullscreen', [
343        (u'Window', False, None),
344        (u'Fullscreen', True, None),
345        ])
346
347    _Preference(u'Transitions', 'transitions', [
348        (u'All', 2, 'config.has_transitions'),
349        (u'Some', 1, 'config.has_transitions and default_transition'),
350        (u'None', 0, 'config.has_transitions'),
351        ])
352
353    # Center
354
355    _Preference(u'Skip', 'skip_unseen', [
356        (u'Seen Messages', False, 'config.allow_skipping and config.has_skipping'),
357        (u'All Messages', True, 'config.allow_skipping and config.has_skipping'),
358        ])
359
360    _JumpPreference(u'Begin Skipping',
361                    '_return_skipping',
362                    'not main_menu',
363                    'config.allow_skipping and config.has_skipping')
364
365
366    _Preference(u'After Choices', 'skip_after_choices', [
367        (u'Stop Skipping', False, 'config.allow_skipping and config.has_skip_after_choice'),
368        (u'Keep Skipping', True, 'config.allow_skipping and config.has_skip_after_choice'),
369        ])
370
371    config.old_names['Keep Skipping'] = 'Continue Skipping'
372
373    _SliderPreference(u'Text Speed', "text_cps", 150, 'config.has_cps')
374    _SliderPreference(u'Auto-Forward Time', "afm_time", 40, 'config.has_afm')
375
376    # Right
377
378    _VolumePreference(u"Music Volume",
379                      'music',
380                      'config.has_music')
381
382    _VolumePreference(u"Sound Volume",
383                      'sfx',
384                      'config.has_sound',
385                      'config.sample_sound',
386                      'sound')
387
388    _VolumePreference(u'Voice Volume',
389                      'voice',
390                      'config.has_voice',
391                      'config.sample_voice',
392                      'voice')
393
394    _JumpPreference(u'Joystick...',
395                    'joystick_preferences_screen',
396                    'renpy.display.joystick.enabled or config.always_has_joystick',
397                    'config.has_joystick')
398
399