584 lines
18 KiB
Text
584 lines
18 KiB
Text
# Copyright 2004-2019 Tom Rothamel <pytom@bishoujo.us>
|
|
#
|
|
# 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 -1100 python in gui:
|
|
from store import config, layout, _preferences, Frame, Null, persistent, Action, DictEquality
|
|
|
|
config.translate_clean_stores.append("gui")
|
|
|
|
_null = Null()
|
|
|
|
def init(width, height):
|
|
"""
|
|
:doc: gui
|
|
|
|
Initializes the gui.
|
|
|
|
`width`
|
|
The width of the default window.
|
|
|
|
`height`
|
|
The height of the default window.
|
|
"""
|
|
|
|
if (not renpy.is_init_phase()) and config.developer:
|
|
raise Exception("gui.init may only be called during the init phase.")
|
|
|
|
config.screen_width = width
|
|
config.screen_height = height
|
|
|
|
layout.defaults()
|
|
|
|
renpy.call_in_new_context("_style_reset")
|
|
|
|
# Defer styles until after translation code runs.
|
|
config.defer_styles = True
|
|
|
|
size = (width, height)
|
|
|
|
if (_preferences.virtual_size is not None) and (_preferences.virtual_size != size):
|
|
_preferences.physical_size = None
|
|
|
|
_preferences.virtual_size = size
|
|
|
|
from store import build
|
|
build.include_old_themes = False
|
|
|
|
if persistent._gui_preference is None:
|
|
persistent._gui_preference = { }
|
|
|
|
if persistent._gui_preference_default is None:
|
|
persistent._gui_preference_default = { }
|
|
|
|
def rebuild():
|
|
"""
|
|
:doc: gui
|
|
|
|
Rebuilds the GUI.
|
|
|
|
Note: This is a very slow function.
|
|
"""
|
|
|
|
renpy.ast.redefine([ "store.gui" ])
|
|
|
|
for i in config.translate_clean_stores:
|
|
renpy.python.clean_store_backup.backup_one("store." + i)
|
|
|
|
# Do the same sort of reset we'd do when changing language, without
|
|
# actually changing the language.
|
|
renpy.change_language(_preferences.language, force=True)
|
|
|
|
not_set = object()
|
|
|
|
def preference(name, default=not_set):
|
|
"""
|
|
:doc: gui_preference
|
|
|
|
This function returns the value of the gui preference with
|
|
`name`.
|
|
|
|
`default`
|
|
If given, this value becomes the default value of the gui
|
|
preference. The default value should be given the first time
|
|
the preference is used.
|
|
"""
|
|
|
|
|
|
prefs = persistent._gui_preference
|
|
defaults = persistent._gui_preference_default
|
|
|
|
if default is not not_set:
|
|
if (name not in defaults) or (defaults[name] != default):
|
|
prefs[name] = default
|
|
defaults[name] = default
|
|
|
|
return prefs[name]
|
|
|
|
|
|
class SetPreference(Action, DictEquality):
|
|
"""
|
|
:doc: gui_preference
|
|
|
|
This Action sets the gui preference with `name` to `value`.
|
|
|
|
`rebuild`
|
|
If true, the default, :func:`gui.rebuild` is called to make
|
|
the changes take effect. This should generally be true, except
|
|
in the case of multiple gui.SetPreference actions, in which case
|
|
it should be False in all but the last one.
|
|
|
|
This is a very slow action, and probably not suitable for use
|
|
when a button is hovered.
|
|
"""
|
|
|
|
def __init__(self, name, value, rebuild=True):
|
|
self.name = name
|
|
self.value = value
|
|
self.rebuild = rebuild
|
|
|
|
def __call__(self):
|
|
prefs = persistent._gui_preference
|
|
|
|
prefs[self.name] = self.value
|
|
rebuild()
|
|
|
|
def get_selected(self):
|
|
prefs = persistent._gui_preference
|
|
return prefs.get(self.name, not_set) == self.value
|
|
|
|
|
|
class TogglePreference(Action, DictEquality):
|
|
"""
|
|
:doc: gui_preference
|
|
|
|
This Action toggles the gui preference with `name` between
|
|
value `a` and value `b`. It is selected if the value is equal
|
|
to `a`.
|
|
|
|
`rebuild`
|
|
If true, the default, :func:`gui.rebuild` is called to make
|
|
the changes take effect. This should generally be true, except
|
|
in the case of multiple gui.SetPreference actions, in which case
|
|
it should be False in all but the last one.
|
|
|
|
This is a very slow action, and probably not suitable for use
|
|
when a button is hovered.
|
|
"""
|
|
|
|
def __init__(self, name, a, b, rebuild=True):
|
|
self.name = name
|
|
self.a = a
|
|
self.b = b
|
|
self.rebuild = rebuild
|
|
|
|
def __call__(self):
|
|
prefs = persistent._gui_preference
|
|
|
|
if prefs[self.name] == self.a:
|
|
prefs[self.name] = self.b
|
|
else:
|
|
prefs[self.name] = self.a
|
|
|
|
rebuild()
|
|
|
|
def get_selected(self):
|
|
prefs = persistent._gui_preference
|
|
return prefs.get(self.name, not_set) == self.a
|
|
|
|
|
|
|
|
renpy.pure("gui.preference")
|
|
renpy.pure("gui.SetPreference")
|
|
renpy.pure("gui.TogglePreference")
|
|
|
|
# The extension used for auto-defined images.
|
|
button_image_extension = ".png"
|
|
|
|
def button_properties(kind):
|
|
"""
|
|
:doc: gui
|
|
|
|
Given a `kind` of button, returns a dictionary giving standard style
|
|
properties for that button. This sets:
|
|
|
|
:propref:`background`
|
|
As described below.
|
|
|
|
:propref:`padding`
|
|
To gui.kind_borders.padding (if it exists).
|
|
|
|
:propref:`xsize`
|
|
To gui.kind_width (if it exists).
|
|
|
|
:propref:`ysize`
|
|
To gui.kind_height (if it exists).
|
|
|
|
(Note that if `kind` is the string "nvl_button", this will look for
|
|
the gui.nvl_button_background variable.)
|
|
|
|
The background is a frame that takes its background picture from
|
|
the first existing one of:
|
|
|
|
* gui/button/kind_[prefix\_].background.png
|
|
* gui/button/[prefix\_].background.png
|
|
|
|
If a gui variables named gui.kind_borders exists, it's
|
|
used. Otherwise, :var:`gui.button_borders` is used. If gui.kind_tile
|
|
exists, it determines if the borders are tiled, else :var:`gui.button_tile`
|
|
controls tiling.
|
|
|
|
For what [prefix\_] means, check out the :ref:`style prefix search <style-prefix-search>`
|
|
documentation.
|
|
"""
|
|
|
|
g = globals()
|
|
|
|
def get(prop):
|
|
if kind + "_" + prop in g:
|
|
return g[kind + "_" + prop]
|
|
|
|
return None
|
|
|
|
borders = get("borders")
|
|
|
|
tile = get("tile")
|
|
if tile is None:
|
|
tile = button_tile
|
|
|
|
backgrounds = [ ]
|
|
|
|
if kind != "button":
|
|
backgrounds.append("gui/button/" + kind[:-7] + "_[prefix_]background" + button_image_extension)
|
|
|
|
backgrounds.append("gui/button/[prefix_]background" + button_image_extension)
|
|
|
|
if renpy.variant("small"):
|
|
backgrounds = [ i.replace("gui/button", "gui/phone/button") for i in backgrounds ] + backgrounds
|
|
|
|
rv = {
|
|
"background" : Frame(backgrounds, borders or button_borders, tile=tile),
|
|
}
|
|
|
|
if borders is not None:
|
|
rv["padding"] = borders.padding
|
|
|
|
width = get("width")
|
|
height = get("height")
|
|
|
|
if width is not None:
|
|
rv["xsize"] = width
|
|
|
|
if height is not None:
|
|
rv["ysize"] = height
|
|
|
|
return rv
|
|
|
|
|
|
def text_properties(kind=None, accent=False):
|
|
"""
|
|
:name: gui.text_properties
|
|
:doc: gui
|
|
|
|
Given a `kind` of button, returns a dictionary giving standard style
|
|
properties for that button. This sets:
|
|
|
|
:propref:`font`
|
|
To gui.kind_text_font, if it exists.
|
|
|
|
:propref:`size`
|
|
To gui.kind_text_size, if it exists.
|
|
|
|
:propref:`xalign`
|
|
To gui.kind_text_xalign, if it exists.
|
|
|
|
:propref:`text_align`
|
|
To gui.kind_text_xalign, if it exists.
|
|
|
|
:propref:`layout`
|
|
To "subtitle" if gui.kind_text_xalign is greater than zero
|
|
and less than one.
|
|
|
|
There are also a number of variables that set the text
|
|
:propref:`color` style property:
|
|
|
|
color
|
|
To gui.kind_text_color, if it exists. If the variable is not
|
|
set, and `accent` is True, sets the text color to the default
|
|
accent color.
|
|
|
|
insensitive_color
|
|
To gui.kind_text_insensitive_color, if it exists.
|
|
|
|
idle_color
|
|
To gui.kind_text_idle_color, if it exists.
|
|
|
|
hover_color
|
|
To gui.kind_text_hover_color, if it exists.
|
|
|
|
selected_color
|
|
To gui.kind_text_selected_color, if it exists.
|
|
|
|
All other :ref:`text style properties <text-style-properties>` are also available. For
|
|
example, gui.kind_text_outlines sets the outlines style property,
|
|
gui.kind_text_kerning sets kerning, and so on.
|
|
"""
|
|
|
|
g = globals()
|
|
|
|
def get(prop):
|
|
|
|
if kind is not None:
|
|
name = kind + "_" + prop
|
|
else:
|
|
name = prop
|
|
|
|
if name in g:
|
|
return g[name]
|
|
|
|
return None
|
|
|
|
rv = { }
|
|
|
|
if accent and (accent_color is not None):
|
|
rv["color"] = accent_color
|
|
|
|
|
|
if kind is not None:
|
|
|
|
xalign = get("text_xalign")
|
|
|
|
if xalign is not None:
|
|
rv["xalign"] = xalign
|
|
rv["text_align"] = xalign
|
|
|
|
if (xalign > 0) and (xalign < 1):
|
|
rv["layout"] = "subtitle"
|
|
|
|
for prefix in renpy.sl2.slparser.STYLE_PREFIXES:
|
|
for property in renpy.sl2.slproperties.text_property_names:
|
|
prop = prefix + property
|
|
|
|
text_prop = "text_" + prop
|
|
|
|
v = get(text_prop)
|
|
|
|
if v is not None:
|
|
rv[prop] = v
|
|
|
|
return rv
|
|
|
|
button_text_properties = text_properties
|
|
|
|
|
|
############################################################################
|
|
# Strings used by the confirm screen.
|
|
|
|
ARE_YOU_SURE = _("Are you sure?")
|
|
DELETE_SAVE = _("Are you sure you want to delete this save?")
|
|
OVERWRITE_SAVE = _("Are you sure you want to overwrite your save?")
|
|
LOADING = _("Loading will lose unsaved progress.\nAre you sure you want to do this?")
|
|
QUIT = _("Are you sure you want to quit?")
|
|
MAIN_MENU = _("Are you sure you want to return to the main menu?\nThis will lose unsaved progress.")
|
|
END_REPLAY = _("Are you sure you want to end the replay?")
|
|
SLOW_SKIP = _("Are you sure you want to begin skipping?")
|
|
FAST_SKIP_SEEN = _("Are you sure you want to skip to the next choice?")
|
|
FAST_SKIP_UNSEEN = _("Are you sure you want to skip unseen dialogue to the next choice?")
|
|
|
|
############################################################################
|
|
# Image generation. This lives here since it wants to read data from
|
|
# the gui variables.
|
|
|
|
# Should we skip backups?
|
|
_skip_backup = False
|
|
|
|
def _gui_images():
|
|
|
|
import store.gui as gui
|
|
from store import config, Color
|
|
|
|
import pygame_sdl2
|
|
import os
|
|
|
|
if not config.developer:
|
|
return
|
|
|
|
phone = renpy.variant("small")
|
|
|
|
class Image(object):
|
|
|
|
def __init__(self, dn, fn, width, height):
|
|
self.s = pygame_sdl2.Surface((width, height), pygame_sdl2.SRCALPHA)
|
|
|
|
|
|
if phone:
|
|
self.fn = os.path.join(config.gamedir, "gui", "phone", dn, fn + ".png")
|
|
else:
|
|
self.fn = os.path.join(config.gamedir, "gui", dn, fn + ".png")
|
|
|
|
self.width = width
|
|
self.height = height
|
|
|
|
def save(self):
|
|
s = self.s
|
|
fn = self.fn
|
|
|
|
dn = os.path.dirname(fn)
|
|
|
|
try:
|
|
os.makedirs(dn, 0o777)
|
|
except:
|
|
pass
|
|
|
|
if os.path.exists(fn):
|
|
|
|
index = 1
|
|
|
|
while True:
|
|
bfn = "{}.{}.bak".format(fn, index)
|
|
|
|
if not os.path.exists(bfn):
|
|
break
|
|
|
|
index += 1
|
|
|
|
if not gui._skip_backup:
|
|
os.rename(fn, bfn)
|
|
|
|
import cStringIO
|
|
sio = cStringIO.StringIO()
|
|
renpy.display.module.save_png(s, sio, 3)
|
|
|
|
with open(fn, "wb") as f:
|
|
f.write(sio.getvalue())
|
|
|
|
def fill(self, color=None):
|
|
if color is None:
|
|
color = gui.accent_color
|
|
|
|
color = tuple(Color(color))
|
|
|
|
self.s.fill(color)
|
|
return self
|
|
|
|
def fill_rect(self, rect, color=None):
|
|
|
|
if color is None:
|
|
color = gui.accent_color
|
|
|
|
color = tuple(Color(color))
|
|
|
|
ss = self.s.subsurface(rect)
|
|
ss.fill(color)
|
|
|
|
return self
|
|
|
|
def fill_left(self, width, color=None):
|
|
self.fill_rect((0, 0, width, self.height))
|
|
return self
|
|
|
|
def scale(fixed, scaled):
|
|
|
|
if fixed is not None:
|
|
return fixed
|
|
|
|
factor = 1.0 * config.screen_height / 720
|
|
return int(scaled * factor)
|
|
|
|
|
|
# Buttons
|
|
width = scale(gui.button_width, 300)
|
|
|
|
if phone:
|
|
height = scale(gui.button_height, 43)
|
|
else:
|
|
height = scale(gui.button_height, 33)
|
|
|
|
check_width = gui.check_button_borders.padding[0]
|
|
check_margin = scale(None, 3)
|
|
check_rect = (
|
|
check_margin,
|
|
gui.check_button_borders.padding[1],
|
|
min(check_width - check_margin, scale(None, 5)),
|
|
height - gui.check_button_borders.padding[1] - gui.check_button_borders.padding[3],
|
|
)
|
|
|
|
radio_width = gui.radio_button_borders.padding[0]
|
|
radio_margin = scale(None, 3)
|
|
|
|
radio_rect = (
|
|
radio_margin,
|
|
gui.radio_button_borders.padding[1],
|
|
min(radio_width - radio_margin, scale(None, 5)),
|
|
height - gui.radio_button_borders.padding[1] - gui.radio_button_borders.padding[3],
|
|
)
|
|
|
|
Image("button", "idle_background", width, height).save()
|
|
Image("button", "hover_background", width, height).save()
|
|
|
|
Image("button", "check_selected_foreground", check_width, height).fill_rect(check_rect).save()
|
|
Image("button", "check_foreground", check_width, height).save()
|
|
|
|
Image("button", "radio_selected_foreground", radio_width, height).fill_rect(radio_rect).save()
|
|
Image("button", "radio_foreground", radio_width, height).save()
|
|
|
|
|
|
# Bars.
|
|
long_size = scale(None, 350)
|
|
|
|
Image("bar", "left", long_size, gui.bar_size).fill(gui.hover_color).save()
|
|
Image("bar", "right", long_size, gui.bar_size).fill(gui.muted_color).save()
|
|
Image("bar", "bottom", gui.bar_size, long_size).fill(gui.hover_color).save()
|
|
Image("bar", "top", gui.bar_size, long_size).fill(gui.muted_color).save()
|
|
|
|
if phone:
|
|
thumb_size = scale(None, 15)
|
|
else:
|
|
thumb_size = scale(None, 10)
|
|
|
|
Image("slider", "horizontal_idle_bar", long_size, gui.slider_size).fill(gui.muted_color).save()
|
|
Image("slider", "horizontal_idle_thumb", thumb_size, gui.slider_size).fill(gui.accent_color).save()
|
|
Image("slider", "horizontal_hover_bar", long_size, gui.slider_size).fill(gui.hover_muted_color).save()
|
|
Image("slider", "horizontal_hover_thumb", thumb_size, gui.slider_size).fill(gui.hover_color).save()
|
|
|
|
Image("slider", "vertical_idle_bar", gui.slider_size, long_size).fill(gui.muted_color).save()
|
|
Image("slider", "vertical_idle_thumb", gui.slider_size, thumb_size).fill(gui.accent_color).save()
|
|
Image("slider", "vertical_hover_bar", gui.slider_size, long_size).fill(gui.hover_muted_color).save()
|
|
Image("slider", "vertical_hover_thumb", gui.slider_size, thumb_size).fill(gui.hover_color).save()
|
|
|
|
|
|
long_size = scale(None, 700)
|
|
|
|
Image("scrollbar", "horizontal_idle_bar", long_size, gui.scrollbar_size).fill(gui.muted_color).save()
|
|
Image("scrollbar", "horizontal_idle_thumb", long_size, gui.scrollbar_size).fill(gui.accent_color).save()
|
|
Image("scrollbar", "horizontal_hover_bar", long_size, gui.scrollbar_size).fill(gui.hover_muted_color).save()
|
|
Image("scrollbar", "horizontal_hover_thumb", long_size, gui.scrollbar_size).fill(gui.hover_color).save()
|
|
|
|
Image("scrollbar", "vertical_idle_bar", gui.scrollbar_size, long_size).fill(gui.muted_color).save()
|
|
Image("scrollbar", "vertical_idle_thumb", gui.scrollbar_size, long_size).fill(gui.accent_color).save()
|
|
Image("scrollbar", "vertical_hover_bar", gui.scrollbar_size, long_size).fill(gui.hover_muted_color).save()
|
|
Image("scrollbar", "vertical_hover_thumb", gui.scrollbar_size, long_size).fill(gui.hover_color).save()
|
|
|
|
sbp = gui.slot_button_borders.padding
|
|
tnx = (gui.slot_button_width - config.thumbnail_width) // 2
|
|
bar_width = scale(None, 5)
|
|
|
|
s = Image("button", "slot_idle_background", gui.slot_button_width, gui.slot_button_height)
|
|
s.fill_rect((tnx, sbp[1], config.thumbnail_width, config.thumbnail_height), gui.muted_color)
|
|
s.save()
|
|
|
|
s = Image("button", "slot_hover_background", gui.slot_button_width, gui.slot_button_height)
|
|
s.fill_rect((tnx, sbp[1], config.thumbnail_width, config.thumbnail_height), gui.hover_muted_color)
|
|
s.fill_rect((0, sbp[1], bar_width, config.thumbnail_height))
|
|
|
|
s.save()
|
|
|
|
|
|
return False
|
|
|
|
renpy.arguments.register_command("gui_images", _gui_images)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|