# Copyright 2004-2022 Tom Rothamel # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. init python: import random import codecs import re import sys import future.utils def theme_names(): """ Gets a list of all of the theme names we know about. """ names = list(theme_data.THEME.keys()) names.sort(key=lambda a : a.lower()) return names def scheme_names(theme): """ Gets a list of the color scheme names corresponding to the given theme. """ names = list(theme_data.THEME[theme].keys()) names.sort(key=lambda a : a.lower()) return names def theme_yinitial(): names = theme_names() if len(names) < 2: return 0 return 1.0 * names.index(current_theme) / (len(names) - 1) def scheme_yinitial(): names = scheme_names(current_theme) if len(names) < 2: return 0 return 1.0 * names.index(current_scheme) / (len(names) - 1) def pick_theme(theme, scheme): """ Returns a theme and scheme that are similar to `theme` and `scheme`. If the theme is known, picks it, otherwise picks a random theme. If the scheme is known for that theme, picks it, otherwise picks a random scheme that is known for the current theme. """ if theme not in theme_data.THEME: theme = random.choice(list(theme_data.THEME)) schemes = theme_data.THEME[theme] if scheme not in schemes: if theme in schemes: scheme = theme else: scheme = random.choice(list(schemes)) return theme, scheme def implement_theme(theme, scheme): """ Implement the current theme. This function uses non-public APIs. """ global showing_theme, showing_scheme if theme == showing_theme and scheme == showing_scheme: return renpy.style.restore(style_backup) future.utils.exec_(theme_data.THEME[theme][scheme], globals(), globals()) # Rebuild the style cache. renpy.style.rebuild(False) # Bust the render cache, so we re-evaluate the styles. renpy.display.interface.kill_textures() showing_theme = theme showing_scheme = scheme renpy.restart_interaction() showing_theme = None showing_scheme = None class SetTheme(Action): def __init__(self, theme): self.theme = theme def __call__(self): global current_theme global current_scheme current_theme, current_scheme = pick_theme(self.theme, current_scheme) implement_theme(current_theme, current_scheme) renpy.restart_interaction() def get_selected(self): return current_theme == self.theme class SetScheme(Action): def __init__(self, scheme): self.scheme = scheme def __call__(self): global current_theme global current_scheme current_theme, current_scheme = pick_theme(current_theme, self.scheme) implement_theme(current_theme, current_scheme) renpy.restart_interaction() def get_selected(self): return current_scheme == self.scheme class PreviewTheme(Action): def __init__(self, theme, scheme): self.theme = theme self.scheme = scheme def __call__(self): theme, scheme = pick_theme(self.theme, self.scheme) implement_theme(theme, scheme) def unhovered(self): if (showing_theme == self.theme and showing_scheme == self.scheme): implement_theme(current_theme, current_scheme) def value_changed(value): return None ########################################################################## # Code to update options.rpy def list_logical_lines(filename): """ This reads in filename, and turns it into a list of logical lines. """ f = codecs.open(filename, "rb", "utf-8") data = f.read() f.close() # The result. rv = [ ] # The current position we're looking at in the buffer. pos = 0 # Looping over the lines in the file. while pos < len(data): # The line that we're building up. line = "" # The number of open parenthesis there are right now. parendepth = 0 # Looping over the characters in a single logical line. while pos < len(data): c = data[pos] if c == '\n' and not parendepth: rv.append(line) pos += 1 # This helps out error checking. line = "" break # Backslash/newline. if c == "\\" and data[pos+1] == "\n": pos += 2 line += "\\\n" continue # Parenthesis. if c in ('(', '[', '{'): parendepth += 1 if c in ('}', ']', ')') and parendepth: parendepth -= 1 # Comments. if c == '#': while data[pos] != '\n': line += data[pos] pos += 1 continue # Strings. if c in ('"', "'", "`"): delim = c line += c pos += 1 escape = False while pos < len(data): c = data[pos] if escape: escape = False pos += 1 line += c continue if c == delim: pos += 1 line += c break if c == '\\': escape = True line += c pos += 1 continue continue line += c pos += 1 if line: rv.append(line) return rv def switch_theme(): """ Switches the theme of the current project to the current theme and color scheme. (As set in current_theme and current_scheme.) """ theme_code = theme_data.THEME[current_theme][current_scheme] # Did we change the file at all? changed = False filename = os.path.join(project.current.path, "game/options.rpy") with codecs.open(filename + ".new", "wb", "utf-8") as out: for l in list_logical_lines(filename): m = re.match(r' theme.(\w+)\(', l) if (not changed) and m and (m.group(1) in theme_data.THEME_FUNCTIONS): l = " " + theme_code changed = True out.write(l + "\n") if changed: try: os.unlink(filename + ".bak") except Exception: pass os.rename(filename, filename + ".bak") os.rename(filename + ".new", filename) else: os.unlink(filename + ".new") interface.error(_("Could not change the theme. Perhaps options.rpy was changed too much.")) # Now give the theme's screen-ops function a chance to make any # necessary changes to the screens.rpy file filename = os.path.join(project.current.path, "game/screens.rpy") changed = False try: with codecs.open(filename + ".new", "wb", "utf-8") as out: lines = list_logical_lines(filename) lines = theme_data.THEME_SCREEN_OPERATIONS[current_theme](lines) if lines != None: for l in lines: out.write(l + "\n") changed = True if changed: try: os.unlink(filename + ".bak") except Exception: pass os.rename(filename, filename + ".bak") os.rename(filename + ".new", filename) except Exception as inst: try: # just in case os.unlink(filename + ".new") except Exception: pass pass def make_style_backup(): """ Call this to back up the styles. This should be called in a translate python block in each translation. """ global style_backup style_backup = renpy.style.backup() config.change_language_callbacks.append(make_style_backup) screen theme_demo: window: style "gm_root" xpadding 5 ypadding 5 grid 1 1: xfill True style_group "prefs" vbox: frame: style_group "pref" has vbox label _("Display") textbutton _("Window") action SelectedIf(True) textbutton _("Fullscreen") action ui.returns(None) textbutton _("Planetarium") action None frame: style_group "pref" has vbox label _("Sound Volume") bar style "slider" value .75 range 1.0 changed value_changed textbutton "Test": action ui.returns(None) style "soundtest_button" init -2 python: style.pref_frame.xfill = True style.pref_frame.xmargin = 5 style.pref_frame.top_margin = 5 style.pref_vbox.xfill = True style.pref_button.size_group = "pref" style.pref_button.xalign = 1.0 style.pref_slider.xmaximum = 192 style.pref_slider.xalign = 1.0 style.soundtest_button.xalign = 1.0 screen choose_theme: default scheme_yadjustment = ui.adjustment() default theme_yadjustment = ui.adjustment() default first = True python: if first: theme_yinitial_value = theme_yinitial() scheme_yinitial_value = scheme_yinitial() else: theme_yinitial_value = None scheme_yinitial_value = None first = False frame: style_group "l" style "l_root" window: has vbox label _("Choose Theme") hbox: yfill True # Theme selector. frame: style "l_indent" bottom_margin HALF_SPACER_HEIGHT xmaximum 225 has vbox label _("Theme") style "l_label_small" viewport: scrollbars "vertical" mousewheel True yadjustment theme_yadjustment yinitial theme_yinitial_value has vbox for i in theme_names(): textbutton "[i]": action SetTheme(i) hovered PreviewTheme(i, current_scheme) style "l_list2" # Color scheme selector. frame: style "l_indent" bottom_margin HALF_SPACER_HEIGHT xmaximum 225 has vbox label _("Color Scheme") style "l_label_small" viewport: scrollbars "vertical" mousewheel True yadjustment scheme_yadjustment yinitial scheme_yinitial_value has vbox for i in scheme_names(current_theme): textbutton "[i]": action SetScheme(i) hovered PreviewTheme(current_theme, i) style "l_list2" # Preview frame: style "l_default" background Frame(PATTERN, 0, 0, tile=True) xpadding 5 ypadding 5 xfill True yfill True xmargin 20 bottom_margin 6 use theme_demo textbutton _("Return") action Jump("front_page") style "l_left_button" textbutton _("Continue") action Return(True) style "l_right_button" label choose_theme_callable: python: current_theme, current_scheme = pick_theme(None, None) implement_theme(current_theme, current_scheme) call screen choose_theme python hide: with interface.error_handling(_("changing the theme")): switch_theme() return label choose_theme: call choose_theme_callable jump front_page