# Copyright 2004-2025 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.

# This file contains code that helps support the development of Ren'Py
# games.

# List styles.

screen _developer:

    layer config.interface_layer
    zorder 1051
    modal True

    frame:
        style_prefix ""

        has side "t c b":
            spacing gui._scale(10)

        label _(u"Developer Menu")

        fixed:
            vbox:

                textbutton _("Interactive Director (D)"):
                    action [ director.Start(), Hide("_developer") ]
                textbutton _("Reload Game (Shift+R)"):
                    action renpy.reload_script
                textbutton _("Console (Shift+O)"):
                    action [ Hide("_developer"), _console.enter ]
                textbutton _("Variable Viewer"):
                    action ui.callsinnewcontext("_variable_viewer")
                textbutton _("Persistent Viewer"):
                    action ui.callsinnewcontext("_persistent_viewer")
                textbutton _("Image Location Picker"):
                    action ui.callsinnewcontext("_image_location_picker")
                textbutton _("Filename List"):
                    action ui.callsinnewcontext("_filename_list")

                if not renpy.get_screen("_image_load_log"):
                    textbutton _("Show Image Load Log (F4)"):
                        action Show("_image_load_log")
                else:
                    textbutton _("Hide Image Load Log (F4)"):
                        action Hide("_image_load_log")

                textbutton _("Image Attributes"):
                    action ui.callsinnewcontext("_image_attributes")

                if not renpy.get_screen("_translation_info"):
                    textbutton _("Show Translation Info"):
                        action Show("_translation_info")
                else:
                    textbutton _("Hide Translation Info"):
                        action Hide("_translation_info")

                if bubble.active:

                    textbutton _("Speech Bubble Editor (Shift+B)"):
                        action [ SetField(bubble.shown, "value", True), Hide("_developer") ]

                if not renpy.get_screen("_filename_and_line"):
                    textbutton _("Show Filename and Line"):
                        action Show("_filename_and_line")
                else:
                    textbutton _("Hide Filename and Line"):
                        action Hide("_filename_and_line")

        hbox:
            spacing gui._scale(25)

            textbutton _(u"Return"):
                action Hide("_developer")
                size_group "developer"

    key "developer" action Hide("_developer")


screen _image_attributes():
    layer config.interface_layer

    frame:
        style_prefix ""
        right_padding 0

        has side "c b":
            spacing gui._scale(10)

        viewport:
            scrollbars "both"
            child_size (4000, 0)
            yfill True
            xfill True
            mousewheel True

            vbox:
                label _("Image Attributes")

                for l in renpy.display.scenelists.ordered_layers:

                    python:

                        hidden = renpy.get_hidden_tags(l)
                        showing = renpy.get_showing_tags(l, sort=True)
                        transforms = renpy.layer_has_transforms(l)
                        transform_list = [ ]

                        if transforms.at_list:
                            transform_list.append("show layer")
                        if transforms.camera:
                            transform_list.append("camera")
                        if transforms.config_layer_transforms:
                            transform_list.append("config.layer_transforms")


                    if not hidden and not showing:
                        continue

                    if l in renpy.config.context_clear_layers:
                        continue

                    text _("Layer [l]:")

                    if transform_list:
                        text _("    (transforms: [', '.join(transform_list)])")

                    for name in hidden:
                        $ attributes = " ".join(renpy.get_attributes(name, layer=l))
                        text _("    [name!q] [attributes!q] (hidden)")

                    for name in showing:
                        $ attributes = " ".join(renpy.get_attributes(name, layer=l))
                        text _("    [name!q] [attributes!q]")

                    text " "

        hbox:
            spacing gui._scale(25)

            textbutton _(u"Return"):
                action Return(True)


    pass

label _image_attributes:

    call _enter_game_menu

    call screen _image_attributes

    return


screen _variable_viewer(title, all_entries, deleted_entries):
    layer config.interface_layer
    zorder 1010
    modal True

    default show_deleted = True
    python:
        if show_deleted:
            entries = all_entries
        else:
            entries = [(n, v) for n, v in all_entries if n not in deleted_entries]

    frame:
        style_prefix ""
        right_padding 0

        has side "t c b":
            spacing gui._scale(10)

        label "[title]"

        viewport:
            scrollbars "both"
            child_size (4000, 0)
            yfill True
            xfill True
            mousewheel True

            vbox:

                if not entries:
                    text _("Nothing to inspect.")

                for vn, value in entries:
                    text "[vn] = [value!q]"

        hbox:
            spacing gui._scale(25)

            textbutton _(u"Return"):
                action Return(True)
            if all_entries and deleted_entries:
                textbutton (_(u"Hide deleted") if show_deleted else _(u"Show deleted")):
                    action ToggleScreenVariable("show_deleted")


label _variable_viewer:

    call _enter_game_menu

    python hide:

        import reprlib
        aRepr = reprlib.Repr()
        aRepr.maxstring = 120

        entries = [ ]
        deleted_entries = set()

        for sn, d in renpy.python.store_dicts.items():

            if sn.startswith("store._"):
                continue

            for vn in d.ever_been_changed:

                if vn.startswith("__00"):
                    continue

                if vn.startswith("_") and not vn.startswith("__"):
                    continue

                if vn not in d:
                    value = "deleted"
                else:
                    value = aRepr.repr(d[vn])

                if vn == "nvl_list":
                    continue

                name = (sn + "." + vn)[6:]

                if vn not in d:
                    deleted_entries.add(name)

                entries.append((name, value))

        entries.sort(key=lambda e : e[0])

        renpy.call_screen("_variable_viewer", _("Variable Viewer"),
                          all_entries=entries, deleted_entries=deleted_entries)

    return


label _persistent_viewer:

    call _enter_game_menu

    python hide:

        import reprlib
        aRepr = reprlib.Repr()
        aRepr.maxstring = 120

        entries = []

        for vn, value in persistent.__dict__.items():

            if vn.startswith("__00"):
                continue

            if vn.startswith("_") and not vn.startswith("__"):
                continue

            value = aRepr.repr(value)

            entries.append((vn, value))

        entries.sort(key=lambda e : e[0])

        renpy.call_screen("_variable_viewer", _("Persistent Viewer"),
                          all_entries=entries, deleted_entries=None)

    return


# Not used any more, but may be in save files.
screen _missing_images:
    pass

init 1050 python:

    if config.developer:

        def __missing_show_callback(name, what, layer):
            if layer != 'master':
                return False

            if not renpy.count_displayables_in_layer(layer):
                p = Placeholder("bg")
            else:
                p = Placeholder()

            return p._duplicate(p._args.copy(args=what))

        def __missing_hide_callback(name, layer):
            if layer != 'master':
                return False

            return True

        def __missing_scene_callback(layer):
            if layer != 'master':
                return False

            return True

        if config.missing_scene is None:
            config.missing_scene = __missing_scene_callback

        if config.missing_show is None:
            config.missing_show = __missing_show_callback

        if config.missing_hide is None:
            config.missing_hide = __missing_hide_callback


init python:

    class __ImageLocationPickerData:

        def __init__(self):
            self.position = None
            self.rectangle = None
            self.clipboard = None

        def set_position(self, position):
            self.position = position
            renpy.restart_interaction()

        def set_rectangle(self, rectangle):
            self.rectangle = rectangle
            renpy.restart_interaction()

        def finish_rectangle(self, rectangle):
            self.rectangle = rectangle
            renpy.restart_interaction()

            x, y, w, h = self.rectangle

            if w or h:
                text = repr((x, y, w, h))
                self.clipboard = __("Rectangle copied to clipboard.")
            else:
                text = repr((x, y))
                self.clipboard = __("Position copied to clipboard.")

            import pygame
            pygame.scrap.put(pygame.scrap.SCRAP_TEXT, text.encode("utf-8"))

        def get_text(self):
            text = [ ]

            if self.rectangle and self.rectangle[2] and self.rectangle[3]:
                x, y, w, h = self.rectangle

                if w or h:
                    text.append(__("Rectangle: %r") % ((x, y, w, h),))

            if self.position:
                text.append(__("Mouse position: %r") % (self.position,))

            if self.clipboard:
                text.append(self.clipboard)

            text.append(__("Right-click or escape to quit."))

            return "\n".join(text)


screen _image_location_picker_image(image_name):
    layer config.interface_layer

    default ilpd = __ImageLocationPickerData()

    viewport:
        edgescroll (40, 400)

        fixed:
            fit_first True

            add image_name
            areapicker:
                persist True
                add "#0ff4"

                position ilpd.set_position
                changed ilpd.set_rectangle
                finished ilpd.finish_rectangle

    text ilpd.get_text():
        align (0.0, 1.0)
        style "_text"
        size 14
        color "#fff"
        outlines [ (1, "#000", 0, 0) ]

    key "game_menu" action Return(True)


screen _image_location_picker(image_files):
    layer config.interface_layer

    default filter = ""

    frame:
        style_prefix ""

        has side "t c b":
            spacing gui._scale(10)

        vbox:
            label _(u"Image Location Picker")

            hbox:
                text _("Type to filter: ")
                input value ScreenVariableInputValue("filter")

        viewport:
            xfill True
            yfill True
            scrollbars "both"
            mousewheel True

            has vbox

            for fn in [ i for i in image_files if filter.lower() in i.lower() ]:

                textbutton "[fn!q]":
                    action Return(fn)
                    style_suffix "small_button"

        hbox:
            spacing gui._scale(25)

            textbutton _(u"Return"):
                action Return(False)


label _image_location_picker:

    call _enter_game_menu

    scene black

    python hide:

        image_files = [ ]

        for fn in renpy.list_files():
            if fn[0] == "_":
                continue

            lfn = fn.lower()

            for i in config.image_extensions:
                if lfn.endswith(i):
                    image_files.append(fn)

        image_files.sort()

        while True:

            rv = renpy.call_screen("_image_location_picker", image_files=image_files)

            if rv is False:
                renpy.jump("_image_location_picker_done")

            renpy.call_screen("_image_location_picker_image", rv)

        renpy.jump("_image_location_picker")

label _image_location_picker_done:
    return


label _filename_list:

    python hide:
        import os

        FILES_TXT = os.path.join(renpy.config.basedir, "files.txt")

        with open(FILES_TXT, "w") as f:
            for dirname, _dirs, files in os.walk(config.gamedir):

                files.sort()
                if dirname.startswith(config.gamedir):
                    dirname = dirname[len(config.gamedir)+1:]

                for fn in files:
                    fn = os.path.join(dirname, fn)
                    print(fn, file=f)

        renpy.launch_editor([ FILES_TXT ], transient=1)

    return


screen _image_load_log():
    layer config.interface_layer
    zorder 1500

    default show_help = True

    style_prefix ""

    python:
        load_log = list(renpy.get_image_load_log(5))
        tex_size, tex_count = renpy.get_texture_size()
        tex_size_mb = tex_size / 1024.0 / 1024.0

        cache_size = renpy.display.im.cache.get_current_size(2)
        cache_size_mb = cache_size * 4.0 / 1024 / 1024
        cache_pct = 100.0 * cache_size / renpy.display.im.cache.cache_limit

    drag:
        draggable True
        focus_mask None
        xpos 0
        ypos 0

        frame:
            style "empty"
            background "#0004"
            xpadding 5
            ypadding 5
            xminimum 200

            has vbox

            text _("Textures: [tex_count] ([tex_size_mb:.1f] MB)"):
                size 14
                color "#fff"

            text _("Image cache: [cache_pct:.1f]% ([cache_size_mb:.1f] MB)"):
                size 14
                color "#fff"

            if load_log:
                text "\n" size 14

            for when, filename, preload in load_log:
                if preload:
                    $ color="#ccffcc"
                    $ prefix=__("✔ ")
                else:
                    $ color="#ffcccc"
                    $ prefix=__("✘ ")

                text "[prefix][filename!q]" color color size 14

            if show_help:
                text _("\n{color=#cfc}✔ predicted image (good){/color}\n{color=#fcc}✘ unpredicted image (bad){/color}\n{color=#fff}Drag to move.{/color}"):
                    size 14

    timer 10.0 action SetScreenVariable("show_help", False)


# Removed, could be in save files.
screen _translation_identifier():
    pass


screen _filename_and_line():
    layer config.interface_layer
    zorder 1500
    style_prefix ""

    # Should help be shown?
    default help = True

    # The filename and line to show.
    $ filename, line = renpy.get_filename_line()

    timer 3 action SetScreenVariable("help", False)

    drag:
        draggable True
        focus_mask None
        xpos 0
        ypos 0

        frame:
            style "empty"
            background "#0004"
            xpadding 5
            ypadding 5
            xminimum 200

            has vbox

            textbutton "[filename]:[line]":
                padding (0, 0)
                text_color "#fff"
                text_hover_color "#bdf"
                text_size 14

                action EditFile(filename, line)

            if help:
                null height 10

                text _("Click to open in editor."):
                    size 14
                    color "#fff"


init 1000 python hide:
    if config.transparent_tile:
        tile = Tile("_transparent_tile.png")
        config.underlay.append(tile)
        renpy.start_predict(tile)

    config.per_frame_screens.append("_image_load_log")

    config.underlay.append(renpy.Keymap(
        image_load_log = ToggleScreen("_image_load_log")
    ))
