import os.path

from ui_dialog import UIDialog, Field
from ui_chooser_dialog import ChooserDialog, ChooserItemButton, ChooserItem

from ui_console import OpenCommand, SaveCommand
from art import ART_DIR, ART_FILE_EXTENSION, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FRAME_DELAY, DEFAULT_LAYER_Z_OFFSET
from palette import PaletteFromFile


class BaseFileDialog(UIDialog):
    
    invalid_filename_error = 'Filename is not valid.'
    filename_exists_error = 'File by that name already exists.'
    
    def get_file_extension(self):
        return ''
    
    def get_dir(self):
        return ''
    
    def get_full_filename(self, filename, dir=None):
        for forbidden_char in self.ui.app.forbidden_filename_chars:
            if forbidden_char in filename:
                return
        full_filename = self.get_dir() + '/' + filename
        full_filename += '.' + self.get_file_extension()
        return full_filename
    
    def is_filename_valid(self, field_number):
        filename = self.field_texts[field_number].strip()
        # filename can't be only whitespace
        if not filename:
            return False, self.invalid_filename_error
        full_filename = self.get_full_filename(filename)
        if not full_filename:
            return False, self.invalid_filename_error
        # if file exists, allow saving but show the warning
        if os.path.exists(full_filename):
            return True, self.filename_exists_error
        return True, None

class NewArtDialog(BaseFileDialog):
    
    title = 'New art'
    field0_label = 'Filename of new art:'
    field2_label = 'Width:'
    field4_label = 'Height:'
    field6_label = 'Save folder:'
    field7_label = ' %s'
    tile_width = 60
    field0_width = 56
    y_spacing = 0
    field1_width = field2_width = UIDialog.default_short_field_width
    fields = [
        Field(label=field0_label, type=str, width=field0_width, oneline=False),
        Field(label='', type=None, width=0, oneline=True),
        Field(label=field2_label, type=int, width=field1_width, oneline=True),
        Field(label='', type=None, width=0, oneline=True),
        Field(label=field4_label, type=int, width=field2_width, oneline=True),
        Field(label='', type=None, width=0, oneline=True),
        Field(label=field6_label, type=None, width=0, oneline=True),
        Field(label=field7_label, type=None, width=0, oneline=True),
        Field(label='', type=None, width=0, oneline=True)
    ]
    confirm_caption = 'Create'
    invalid_width_error = 'Invalid width.'
    invalid_height_error = 'Invalid height.'
    
    def get_initial_field_text(self, field_number):
        if field_number == 0:
            return 'new%s' % len(self.ui.app.art_loaded_for_edit)
        elif field_number == 2:
            return str(DEFAULT_WIDTH)
        elif field_number == 4:
            return str(DEFAULT_HEIGHT)
        return ''
    
    def get_field_label(self, field_index):
        label = self.fields[field_index].label
        # show dir art will be saved into
        if field_index == 7:
            label %= self.get_dir()
        return label
    
    def get_file_extension(self):
        return ART_FILE_EXTENSION
    
    def get_dir(self):
        return self.ui.app.documents_dir + ART_DIR
    
    def is_input_valid(self):
        "warn if file already exists, dimensions must be >0 and <= max"
        if not self.is_valid_dimension(self.field_texts[2], self.ui.app.max_art_width):
            return False, self.invalid_width_error
        if not self.is_valid_dimension(self.field_texts[4], self.ui.app.max_art_height):
            return False, self.invalid_height_error
        return self.is_filename_valid(0)
    
    def is_valid_dimension(self, dimension, max_dimension):
        try: dimension = int(dimension)
        except: return False
        return 0 < dimension <= max_dimension
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        name = self.field_texts[0]
        w, h = int(self.field_texts[2]), int(self.field_texts[4])
        self.ui.app.new_art_for_edit(name, w, h)
        self.ui.app.log('Created %s.psci with size %s x %s' % (name, w, h))
        self.dismiss()


class SaveAsDialog(BaseFileDialog):
    
    title = 'Save art'
    field0_label = 'New filename for art:'
    field2_label = 'Save folder:'
    field3_label = ' %s'
    tile_width = 60
    field0_width = 56
    y_spacing = 0
    fields = [
        Field(label=field0_label, type=str, width=field0_width, oneline=False),
        Field(label='', type=None, width=0, oneline=True),
        Field(label=field2_label, type=None, width=0, oneline=True),
        Field(label=field3_label, type=None, width=0, oneline=True),
        Field(label='', type=None, width=0, oneline=True)
    ]
    confirm_caption = 'Save'
    always_redraw_labels = True
    
    def get_initial_field_text(self, field_number):
        if field_number == 0:
            # special case: if opening playscii/art/new, change
            # it to documents dir to avoid writing to application dir
            # (still possible if you open other files)
            if os.path.dirname(self.ui.active_art.filename) == ART_DIR[:-1]:
                self.ui.active_art.filename = self.ui.app.documents_dir + self.ui.active_art.filename
            # TODO: handle other files from app dir as well? not as important
            filename = os.path.basename(self.ui.active_art.filename)
            filename = os.path.splitext(filename)[0]
            return filename
        return ''
    
    def get_file_extension(self):
        """
        Return file extension this dialog saves as; other dialogs
        are based on this class so we don't want to hardcore .psci
        """
        return ART_FILE_EXTENSION
    
    def get_dir(self):
        return os.path.dirname(self.ui.active_art.filename)
    
    def get_field_label(self, field_index):
        label = self.fields[field_index].label
        # show dir art will be saved into
        if field_index == 3:
            label %= self.get_dir()
        return label
    
    def is_input_valid(self):
        return self.is_filename_valid(0)
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        SaveCommand.execute(self.ui.console, [self.field_texts[0]])
        self.dismiss()

class ConvertItemButton(ChooserItemButton):
    width = 15
    big_width = 20

class ConvertChooserItem(ChooserItem):
    
    def picked(self, element):
        
        # TODO: following is c+p'd from BaseFileChooserItem.picked,
        # move this functionality into ChooserItem and override in BFCI?
        
        # if this is different from the last clicked item, pick it
        if element.selected_item_index != self.index:
            ChooserItem.picked(self, element)
            element.first_selection_made = True
            return
        # if we haven't yet clicked something in this view, require another
        # click before opening it (consistent double click behavior for
        # initial selections)
        if not element.first_selection_made:
            element.first_selection_made = True
            return
        element.confirm_pressed()
        element.first_selection_made = False
    
    def get_description_lines(self):
        return self.description.split('\n')

class ConvertFileDialog(ChooserDialog):
    "Common functionality for importer and exporter selection dialogs"
    tile_width, big_width = 85, 90
    tile_height, big_height = 15, 25
    confirm_caption = 'Choose'
    show_preview_image = False
    item_button_class = ConvertItemButton
    chooser_item_class = ConvertChooserItem
    
    def get_converters(self):
        return []
    
    def get_items(self):
        items = []
        converters = self.get_converters()
        # sort alphabetically
        converters.sort(key=lambda item: item.format_name.lower())
        i = 0
        for converter in converters:
            item = self.chooser_item_class(i, converter.__name__)
            item.converter_class = converter
            item.label = converter.format_name
            item.description = converter.format_description
            items.append(item)
            i += 1
        return items


class ImportFileDialog(ConvertFileDialog):
    title = 'Choose an importer'
    
    def get_converters(self):
        return self.ui.app.get_importers()
    
    def confirm_pressed(self):
        # open file select dialog so user can choose what to import
        item = self.get_selected_item()
        self.ui.app.importer = item.converter_class
        if not self.ui.app.importer:
            return
        self.dismiss()
        self.ui.open_dialog(self.ui.app.importer.file_chooser_dialog_class)

class ImportOptionsDialog(UIDialog):
    "Generic base class for importer options"
    confirm_caption = 'Import'
    def do_import(app, filename, options):
        "Common 'run importer' code for end of import options dialog"
        # if importer needs no options, run it
        importer = app.importer(app, filename, options)
        if importer.success:
            if app.importer.completes_instantly:
                app.log('Imported %s successfully.' % filename)
            app.importer = None

class ExportOptionsDialog(UIDialog):
    "Generic base class for exporter options"
    confirm_caption = 'Export'
    def do_export(app, filename, options):
        "Common 'run exporter' code for end of import options dialog"
        # if importer needs no options, run it
        exporter = app.exporter(app, filename, options)
        if exporter.success:
            app.log('Exported %s successfully.' % exporter.out_filename)

class ExportFileDialog(ConvertFileDialog):
    title = 'Choose an exporter'
    
    def get_converters(self):
        return self.ui.app.get_exporters()
    
    def confirm_pressed(self):
        # open file select dialog so user can choose what to import
        item = self.get_selected_item()
        self.ui.app.exporter = item.converter_class
        if not self.ui.app.exporter:
            return
        self.dismiss()
        self.ui.open_dialog(ExportFilenameInputDialog)


class ExportFilenameInputDialog(SaveAsDialog):
    title = 'Export art'
    field0_label = 'New filename for exported art:'
    confirm_caption = 'Export'
    
    def get_initial_field_text(self, field_number):
        # base output filename on art filename
        if field_number == 0:
            out_filename = self.ui.active_art.filename
            out_filename = os.path.basename(out_filename)
            out_filename = os.path.splitext(out_filename)[0]
            return out_filename
    
    def get_file_extension(self):
        return self.ui.app.exporter.file_extension
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        filename = self.field_texts[0]
        self.dismiss()
        # invoke options dialog if exporter has one, else invoke exporter
        if self.ui.app.exporter.options_dialog_class:
            # pass filename into new dialog
            options = {'filename': filename}
            self.ui.open_dialog(self.ui.app.exporter.options_dialog_class,
                                options)
        else:
            ExportOptionsDialog.do_export(self.ui.app, filename, {})


class QuitUnsavedChangesDialog(UIDialog):
    
    title = 'Unsaved changes'
    message = 'Save changes to %s?'
    confirm_caption = 'Save'
    other_button_visible = True
    other_caption = "Don't Save"
    
    def confirm_pressed(self):
        SaveCommand.execute(self.ui.console, [])
        self.dismiss()
        # try again, see if another art has unsaved changes
        self.ui.app.il.BIND_quit()
    
    def other_pressed(self):
        # kind of a hack: make the check BIND_quit does come up false
        # for this art. externalities fairly minor.
        self.ui.active_art.unsaved_changes = False
        self.dismiss()
        self.ui.app.il.BIND_quit()
    
    def get_message(self):
        # get base name (ie no dirs)
        filename = os.path.basename(self.ui.active_art.filename)
        return [self.message % filename]


class CloseUnsavedChangesDialog(QuitUnsavedChangesDialog):
    
    def confirm_pressed(self):
        SaveCommand.execute(self.ui.console, [])
        self.dismiss()
        self.ui.app.il.BIND_close_art()
    
    def other_pressed(self):
        self.ui.active_art.unsaved_changes = False
        self.dismiss()
        self.ui.app.il.BIND_close_art()


class RevertChangesDialog(UIDialog):
    
    title = 'Revert changes'
    message = 'Revert changes to %s?'
    confirm_caption = 'Revert'
    
    def confirm_pressed(self):
        self.ui.app.revert_active_art()
        self.dismiss()
    
    def get_message(self):
        filename = os.path.basename(self.ui.active_art.filename)
        return [self.message % filename]


class ResizeArtDialog(UIDialog):
    
    title = 'Resize art'
    field_width = UIDialog.default_short_field_width
    field0_label = 'New Width:'
    field1_label = 'New Height:'
    field2_label = 'Crop Start X:'
    field3_label = 'Crop Start Y:'
    field4_label = 'Fill new tiles with BG color'
    fields = [
        Field(label=field0_label, type=int, width=field_width, oneline=True),
        Field(label=field1_label, type=int, width=field_width, oneline=True),
        Field(label=field2_label, type=int, width=field_width, oneline=True),
        Field(label=field3_label, type=int, width=field_width, oneline=True),
        Field(label=field4_label, type=bool, width=0, oneline=True)
    ]
    confirm_caption = 'Resize'
    invalid_width_error = 'Invalid width.'
    invalid_height_error = 'Invalid height.'
    invalid_start_error = 'Invalid crop origin.'
    
    def get_initial_field_text(self, field_number):
        if field_number == 0:
            return str(self.ui.active_art.width)
        elif field_number == 1:
            return str(self.ui.active_art.height)
        elif field_number == 4:
            return UIDialog.true_field_text
        else:
            return '0'
    
    def is_input_valid(self):
        "file can't already exist, dimensions must be >0 and <= max"
        if not self.is_valid_dimension(self.field_texts[0], self.ui.app.max_art_width):
            return False, self.invalid_width_error
        if not self.is_valid_dimension(self.field_texts[1], self.ui.app.max_art_height):
            return False, self.invalid_height_error
        try: int(self.field_texts[2])
        except: return False, self.invalid_start_error
        if not 0 <= int(self.field_texts[2]) < self.ui.active_art.width:
            return False, self.invalid_start_error
        try: int(self.field_texts[3])
        except: return False, self.invalid_start_error
        if not 0 <= int(self.field_texts[3]) < self.ui.active_art.height:
            return False, self.invalid_start_error
        return True, None
    
    def is_valid_dimension(self, dimension, max_dimension):
        try: dimension = int(dimension)
        except: return False
        return 0 < dimension <= max_dimension
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        w, h = int(self.field_texts[0]), int(self.field_texts[1])
        start_x, start_y = int(self.field_texts[2]), int(self.field_texts[3])
        bg_fill = bool(self.field_texts[4].strip())
        self.ui.resize_art(self.ui.active_art, w, h, start_x, start_y, bg_fill)
        self.dismiss()


#
# layer menu dialogs
#

class AddFrameDialog(UIDialog):
    
    title = 'Add new frame'
    field0_label = 'Index to add frame before:'
    field1_label = 'Hold time (in seconds) for new frame:'
    field_width = UIDialog.default_short_field_width
    fields = [
        Field(label=field0_label, type=int, width=field_width, oneline=True),
        Field(label=field1_label, type=float, width=field_width, oneline=False)
    ]
    confirm_caption = 'Add'
    invalid_index_error = 'Invalid index. (1-%s allowed)'
    invalid_delay_error = 'Invalid hold time.'
    
    def get_initial_field_text(self, field_number):
        if field_number == 0:
            return str(self.ui.active_art.frames + 1)
        elif field_number == 1:
            return str(DEFAULT_FRAME_DELAY)
    
    def is_valid_frame_index(self, index):
        try: index = int(index)
        except: return False
        if index < 1 or index > self.ui.active_art.frames + 1:
            return False
        return True
    
    def is_valid_frame_delay(self, delay):
        try: delay = float(delay)
        except: return False
        return delay > 0
    
    def is_input_valid(self):
        if not self.is_valid_frame_index(self.field_texts[0]):
            return False, self.invalid_index_error % str(self.ui.active_art.frames + 1)
        if not self.is_valid_frame_delay(self.field_texts[1]):
            return False, self.invalid_delay_error
        return True, None
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        index = int(self.field_texts[0])
        delay = float(self.field_texts[1])
        self.ui.active_art.insert_frame_before_index(index - 1, delay)
        self.dismiss()

class DuplicateFrameDialog(AddFrameDialog):
    title = 'Duplicate frame'
    confirm_caption = 'Duplicate'
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        index = int(self.field_texts[0])
        delay = float(self.field_texts[1])
        self.ui.active_art.duplicate_frame(self.ui.active_art.active_frame, index - 1, delay)
        self.dismiss()

class FrameDelayDialog(AddFrameDialog):
    
    field0_label = 'New hold time (in seconds) for frame:'
    field_width = UIDialog.default_short_field_width
    fields = [
        Field(label=field0_label, type=float, width=field_width, oneline=False)
    ]
    confirm_caption = 'Set'
    
    def get_initial_field_text(self, field_number):
        if field_number == 0:
            return str(self.ui.active_art.frame_delays[self.ui.active_art.active_frame])
    
    def is_input_valid(self):
        if not self.is_valid_frame_delay(self.field_texts[0]):
            return False, self.invalid_delay_error
        return True, None
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        delay = float(self.field_texts[0])
        self.ui.active_art.frame_delays[self.ui.active_art.active_frame] = delay
        self.dismiss()

class FrameDelayAllDialog(FrameDelayDialog):
    field0_label = 'New hold time (in seconds) for all frames:'
    field_width = UIDialog.default_short_field_width
    fields = [
        Field(label=field0_label, type=float, width=field_width, oneline=False)
    ]
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        delay = float(self.field_texts[0])
        for i in range(self.ui.active_art.frames):
            self.ui.active_art.frame_delays[i] = delay
        self.dismiss()

class FrameIndexDialog(AddFrameDialog):
    field0_label = 'Move this frame before index:'
    field_width = UIDialog.default_short_field_width
    fields = [
        Field(label=field0_label, type=int, width=field_width, oneline=False)
    ]
    confirm_caption = 'Set'
    
    def is_input_valid(self):
        if not self.is_valid_frame_index(self.field_texts[0]):
            return False, self.invalid_index_error % str(self.ui.active_art.frames + 1)
        return True, None
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        # set new frame index (effectively moving it in the sequence)
        dest_index = int(self.field_texts[0])
        self.ui.active_art.move_frame_to_index(self.ui.active_art.active_frame, dest_index)
        self.dismiss()


#
# layer menu dialogs
#

class AddLayerDialog(UIDialog):
    
    title = 'Add new layer'
    field0_label = 'Name for new layer:'
    field1_label = 'Z-depth for new layer:'
    field0_width = UIDialog.default_field_width
    field1_width = UIDialog.default_short_field_width
    fields = [
        Field(label=field0_label, type=str, width=field0_width, oneline=False),
        Field(label=field1_label, type=float, width=field1_width, oneline=True)
    ]
    confirm_caption = 'Add'
    name_exists_error = 'Layer by that name already exists.'
    invalid_z_error = 'Invalid number.'
    
    def get_initial_field_text(self, field_number):
        if field_number == 0:
            return 'Layer %s' % str(self.ui.active_art.layers + 1)
        elif field_number == 1:
            return str(self.ui.active_art.layers_z[self.ui.active_art.active_layer] + DEFAULT_LAYER_Z_OFFSET)
    
    def is_valid_layer_name(self, name, exclude_active_layer=False):
        for i,layer_name in enumerate(self.ui.active_art.layer_names):
            if exclude_active_layer and i == self.ui.active_layer:
                continue
            if layer_name == name:
                return False
        return True
    
    def is_input_valid(self):
        valid_name = self.is_valid_layer_name(self.field_texts[0])
        if not valid_name:
            return False, self.name_exists_error
        try: z = float(self.field_texts[1])
        except: return False, self.invalid_z_error
        return True, None
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        name = self.field_texts[0]
        z = float(self.field_texts[1])
        self.ui.active_art.add_layer(z, name)
        self.dismiss()


class DuplicateLayerDialog(AddLayerDialog):
    title = 'Duplicate layer'
    confirm_caption = 'Duplicate'
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        name = self.field_texts[0]
        z = float(self.field_texts[1])
        self.ui.active_art.duplicate_layer(self.ui.active_art.active_layer, z, name)
        self.dismiss()


class SetLayerNameDialog(AddLayerDialog):
    
    title = 'Set layer name'
    field0_label = 'New name for this layer:'
    field_width = UIDialog.default_field_width
    fields = [
        Field(label=field0_label, type=str, width=field_width, oneline=False)
    ]
    confirm_caption = 'Rename'
    
    def confirm_pressed(self):
        new_name = self.field_texts[0]
        self.ui.active_art.layer_names[self.ui.active_art.active_layer] = new_name
        self.ui.active_art.set_unsaved_changes(True)
        self.dismiss()


class SetLayerZDialog(UIDialog):
    title = 'Set layer Z-depth'
    field0_label = 'Z-depth for layer:'
    field_width = UIDialog.default_short_field_width
    fields = [
        Field(label=field0_label, type=float, width=field_width, oneline=False)
    ]
    confirm_caption = 'Set'
    invalid_z_error = 'Invalid number.'
    
    def get_initial_field_text(self, field_number):
        # populate with existing z
        if field_number == 0:
            return str(self.ui.active_art.layers_z[self.ui.active_art.active_layer])
    
    def is_input_valid(self):
        try: z = float(self.field_texts[0])
        except: return False, self.invalid_z_error
        return True, None
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        new_z = float(self.field_texts[0])
        self.ui.active_art.layers_z[self.ui.active_art.active_layer] = new_z
        self.ui.active_art.set_unsaved_changes(True)
        self.ui.app.grid.reset()
        self.dismiss()


class PaletteFromFileDialog(UIDialog):
    title = 'Create palette from file'
    field0_label = 'Filename to create palette from:'
    field1_label = 'Filename for new palette:'
    field2_label = 'Colors in new palette:'
    field0_width = field1_width = UIDialog.default_field_width
    field2_width = UIDialog.default_short_field_width
    fields = [
        Field(label=field0_label, type=str, width=field0_width, oneline=False),
        Field(label=field1_label, type=str, width=field1_width, oneline=False),
        Field(label=field2_label, type=int, width=field2_width, oneline=True)
    ]
    confirm_caption = 'Create'
    invalid_color_error = 'Palettes must be between 2 and 256 colors.'
    bad_output_filename_error = 'Enter a filename for the new palette.'
    
    def get_initial_field_text(self, field_number):
        # NOTE: PaletteFromImageChooserDialog.confirm_pressed which invokes us
        # sets fields 0 and 1
        if field_number == 2:
            return str(256)
        return ''
    
    def valid_colors(self, colors):
        try: c = int(colors)
        except: return False
        return 2 <= c <= 256
    
    def is_input_valid(self):
        valid_colors = self.valid_colors(self.field_texts[2])
        if not valid_colors:
            return False, self.invalid_color_error
        if not self.field_texts[1].strip():
            return False, self.bad_output_filename_error
        return True, None
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        src_filename = self.field_texts[0]
        palette_filename = self.field_texts[1]
        colors = int(self.field_texts[2])
        new_pal = PaletteFromFile(self.ui.app, src_filename, palette_filename, colors)
        self.dismiss()


class SetCameraZoomDialog(UIDialog):
    title = 'Set camera zoom'
    field0_label = 'New camera zoom %:'
    field_width = UIDialog.default_short_field_width
    fields = [
        Field(label=field0_label, type=float, width=field_width, oneline=True)
    ]
    confirm_caption = 'Set'
    invalid_zoom_error = 'Zoom % must be a number greater than zero.'
    all_modes_visible = True
    game_mode_visible = True
    
    def get_initial_field_text(self, field_number):
        if field_number == 0:
            return '%.1f' % self.ui.app.camera.get_current_zoom_pct()
        return ''
    
    def is_input_valid(self):
        try: zoom = float(self.field_texts[0])
        except: return False, self.invalid_zoom_error
        if zoom <= 0:
            return False, self.invalid_zoom_error
        return True, None
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        new_zoom_pct = float(self.field_texts[0])
        camera = self.ui.app.camera
        camera.z = camera.get_base_zoom() / (new_zoom_pct / 100)
        self.dismiss()


class OverlayImageOpacityDialog(UIDialog):
    title = 'Set overlay image opacity'
    field0_label = 'New overlay opacity %:'
    field_width = UIDialog.default_short_field_width
    fields = [
        Field(label=field0_label, type=float, width=field_width, oneline=True)
    ]
    confirm_caption = 'Set'
    invalid_opacity_error = 'Opacity % must be between 0 and 100.'
    
    def get_initial_field_text(self, field_number):
        if field_number == 0:
            return '%.1f' % (self.ui.app.overlay_renderable.alpha * 100)
        return ''
    
    def is_input_valid(self):
        try: opacity = float(self.field_texts[0])
        except: return False, self.invalid_opacity_error
        if opacity <= 0 or opacity > 100:
            return False, self.invalid_opacity_error
        return True, None
    
    def confirm_pressed(self):
        valid, reason = self.is_input_valid()
        if not valid: return
        new_opacity = float(self.field_texts[0])
        self.ui.app.overlay_renderable.alpha = new_opacity / 100
        self.dismiss()
