diff --git a/extensions-builtin/mobile/javascript/mobile.js b/extensions-builtin/mobile/javascript/mobile.js
index 12cae4b7576..3784b65c71a 100644
--- a/extensions-builtin/mobile/javascript/mobile.js
+++ b/extensions-builtin/mobile/javascript/mobile.js
@@ -18,7 +18,13 @@ function reportWindowSize() {
for (var tab of ["txt2img", "img2img"]) {
var button = gradioApp().getElementById(tab + '_generate_box');
- var target = gradioApp().getElementById(currentlyMobile ? tab + '_results' : tab + '_actions_column');
+ var target = null;
+ if (currentlyMobile) {
+ target = gradioApp().getElementById(tab + '_results');
+ } else {
+ target = gradioApp().getElementById(tab + '_actions_column') || gradioApp().getElementById(tab + '_generate_row');
+ }
+
target.insertBefore(button, target.firstElementChild);
}
}
diff --git a/modules/shared_options.py b/modules/shared_options.py
index 8630d4741ca..9b12191d2b2 100644
--- a/modules/shared_options.py
+++ b/modules/shared_options.py
@@ -232,6 +232,8 @@
"localization": OptionInfo("None", "Localization", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)).needs_reload_ui(),
"gradio_theme": OptionInfo("Default", "Gradio theme", ui_components.DropdownEditable, lambda: {"choices": ["Default"] + shared_gradio_themes.gradio_hf_hub_themes}).info("you can also manually enter any of themes from the gallery.").needs_reload_ui(),
"gradio_themes_cache": OptionInfo(True, "Cache gradio themes locally").info("disable to update the selected Gradio theme"),
+ "move_toprow_to_settings_column": OptionInfo(False, "Move top row (prompt, generate button) to settings column").needs_reload_ui(),
+ "sticky_generate_button": OptionInfo(False, "Make generate button stick during scroll").info("only applies when the setting above is ticked").needs_reload_ui(),
"gallery_height": OptionInfo("", "Gallery height", gr.Textbox).info("an be any valid CSS value").needs_reload_ui(),
"return_grid": OptionInfo(True, "Show grid in results for web"),
"do_not_show_images": OptionInfo(False, "Do not show any images in results for web"),
diff --git a/modules/ui.py b/modules/ui.py
index 01f77849d25..9f4b99b1122 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -4,6 +4,7 @@
import sys
from functools import reduce
import warnings
+from contextlib import nullcontext
import gradio as gr
import gradio.utils
@@ -169,69 +170,58 @@ def update_token_counter(text, steps):
return f"{token_count}/{max_length}"
+class ToprowPrompt:
+ """Creates prompt components for Toprow"""
+ def __init__(self, id_part):
+ self.prompt = gr.Textbox(label="Prompt", elem_id=f"{id_part}_prompt", show_label=False, lines=3, placeholder="Prompt (press Ctrl+Enter or Alt+Enter to generate)", elem_classes=["prompt"])
+ self.negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)", elem_classes=["prompt"])
+
+
class Toprow:
"""Creates a top row UI with prompts, generate button, styles, extra little buttons for things, and enables some functionality related to their operation"""
- def __init__(self, is_img2img):
+ def __init__(self, is_img2img: bool, toprow_prompt: ToprowPrompt, in_settings_column=False):
id_part = "img2img" if is_img2img else "txt2img"
self.id_part = id_part
+ self.in_settings_column = in_settings_column
+ self.prompt = toprow_prompt.prompt
+ self.negative_prompt = toprow_prompt.negative_prompt
+ self.flex_revert = ["flex-basis-revert"] if in_settings_column else []
+
+ if in_settings_column and opts.sticky_generate_button:
+ with gr.Row(elem_id=f"{id_part}_generate_row", elem_classes=["generate-sticky"] + self.flex_revert):
+ self._create_generate_box()
with gr.Row(elem_id=f"{id_part}_toprow", variant="compact"):
+ if in_settings_column and not opts.sticky_generate_button:
+ with gr.Row(elem_id=f"{id_part}_generate_row", elem_classes=self.flex_revert):
+ self._create_generate_box()
+
with gr.Column(elem_id=f"{id_part}_prompt_container", scale=6):
- with gr.Row():
- with gr.Column(scale=80):
- with gr.Row():
- self.prompt = gr.Textbox(label="Prompt", elem_id=f"{id_part}_prompt", show_label=False, lines=3, placeholder="Prompt (press Ctrl+Enter or Alt+Enter to generate)", elem_classes=["prompt"])
- self.prompt_img = gr.File(label="", elem_id=f"{id_part}_prompt_image", file_count="single", type="binary", visible=False)
+ with gr.Column() if self.in_settings_column else nullcontext():
+ with gr.Row():
+ toprow_prompt.prompt.render()
+ self.prompt_img = gr.File(label="", elem_id=f"{id_part}_prompt_image", file_count="single", type="binary", visible=False)
- with gr.Row():
- with gr.Column(scale=80):
- with gr.Row():
- self.negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)", elem_classes=["prompt"])
+ with gr.Row():
+ self.negative_prompt.render()
self.button_interrogate = None
self.button_deepbooru = None
if is_img2img:
- with gr.Column(scale=1, elem_classes="interrogate-col"):
+ with gr.Column(scale=1, elem_classes="interrogate-col") if not in_settings_column else gr.Row():
self.button_interrogate = gr.Button('Interrogate\nCLIP', elem_id="interrogate")
self.button_deepbooru = gr.Button('Interrogate\nDeepBooru', elem_id="deepbooru")
- with gr.Column(scale=1, elem_id=f"{id_part}_actions_column"):
- with gr.Row(elem_id=f"{id_part}_generate_box", elem_classes="generate-box"):
- self.interrupt = gr.Button('Interrupt', elem_id=f"{id_part}_interrupt", elem_classes="generate-box-interrupt")
- self.skip = gr.Button('Skip', elem_id=f"{id_part}_skip", elem_classes="generate-box-skip")
- self.submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary')
-
- self.skip.click(
- fn=lambda: shared.state.skip(),
- inputs=[],
- outputs=[],
- )
-
- self.interrupt.click(
- fn=lambda: shared.state.interrupt(),
- inputs=[],
- outputs=[],
- )
-
- with gr.Row(elem_id=f"{id_part}_tools"):
- self.paste = ToolButton(value=paste_symbol, elem_id="paste")
- self.clear_prompt_button = ToolButton(value=clear_prompt_symbol, elem_id=f"{id_part}_clear_prompt")
- self.restore_progress_button = ToolButton(value=restore_progress_symbol, elem_id=f"{id_part}_restore_progress", visible=False)
-
- self.token_counter = gr.HTML(value="0/75", elem_id=f"{id_part}_token_counter", elem_classes=["token-counter"])
- self.token_button = gr.Button(visible=False, elem_id=f"{id_part}_token_button")
- self.negative_token_counter = gr.HTML(value="0/75", elem_id=f"{id_part}_negative_token_counter", elem_classes=["token-counter"])
- self.negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button")
-
- self.clear_prompt_button.click(
- fn=lambda *x: x,
- _js="confirm_clear_prompt",
- inputs=[self.prompt, self.negative_prompt],
- outputs=[self.prompt, self.negative_prompt],
- )
+ if in_settings_column:
+ with gr.Row(elem_classes=self.flex_revert):
+ self._create_styles(extra_buttons=self._create_tools)
- self.ui_styles = ui_prompt_styles.UiPromptStyles(id_part, self.prompt, self.negative_prompt)
+ if not in_settings_column:
+ with gr.Column(scale=1, elem_id=f"{id_part}_actions_column"):
+ self._create_generate_box()
+ self._create_tools()
+ self._create_styles()
self.prompt_img.change(
fn=modules.images.image_data,
@@ -240,6 +230,46 @@ def __init__(self, is_img2img):
show_progress=False,
)
+ def _create_generate_box(self):
+ with gr.Row(elem_id=f"{self.id_part}_generate_box", elem_classes=["generate-box"] + self.flex_revert):
+ self.interrupt = gr.Button('Interrupt', elem_id=f"{self.id_part}_interrupt", elem_classes="generate-box-interrupt")
+ self.skip = gr.Button('Skip', elem_id=f"{self.id_part}_skip", elem_classes="generate-box-skip")
+ self.submit = gr.Button('Generate', elem_id=f"{self.id_part}_generate", variant='primary')
+
+ self.skip.click(
+ fn=lambda: shared.state.skip(),
+ inputs=[],
+ outputs=[],
+ )
+
+ self.interrupt.click(
+ fn=lambda: shared.state.interrupt(),
+ inputs=[],
+ outputs=[],
+ )
+
+ def _create_tools(self):
+ with gr.Row(elem_id=f"{self.id_part}_tools", elem_classes=self.flex_revert) if not self.in_settings_column else nullcontext():
+ self.paste = ToolButton(value=paste_symbol, elem_id="paste")
+ self.clear_prompt_button = ToolButton(value=clear_prompt_symbol, elem_id=f"{self.id_part}_clear_prompt")
+ self.restore_progress_button = ToolButton(value=restore_progress_symbol, elem_id=f"{self.id_part}_restore_progress", visible=False)
+
+ self.token_counter = gr.HTML(value="0/75", elem_id=f"{self.id_part}_token_counter", elem_classes=["token-counter"])
+ self.token_button = gr.Button(visible=False, elem_id=f"{self.id_part}_token_button")
+ self.negative_token_counter = gr.HTML(value="0/75", elem_id=f"{self.id_part}_negative_token_counter", elem_classes=["token-counter"])
+ self.negative_token_button = gr.Button(visible=False, elem_id=f"{self.id_part}_negative_token_button")
+
+ self.clear_prompt_button.click(
+ fn=lambda *x: x,
+ _js="confirm_clear_prompt",
+ inputs=[self.prompt, self.negative_prompt],
+ outputs=[self.prompt, self.negative_prompt],
+ )
+
+ def _create_styles(self, extra_buttons=None):
+ with gr.Row(elem_classes=self.flex_revert) if self.in_settings_column else nullcontext():
+ self.ui_styles = ui_prompt_styles.UiPromptStyles(self.id_part, self.prompt, self.negative_prompt, extra_buttons=extra_buttons)
+
def setup_progressbar(*args, **kwargs):
pass
@@ -325,8 +355,10 @@ def create_ui():
scripts.scripts_current = scripts.scripts_txt2img
scripts.scripts_txt2img.initialize_scripts(is_img2img=False)
+ txt2img_prompt = ToprowPrompt("txt2img")
with gr.Blocks(analytics_enabled=False) as txt2img_interface:
- toprow = Toprow(is_img2img=False)
+ if not opts.move_toprow_to_settings_column:
+ toprow = Toprow(is_img2img=False, toprow_prompt=txt2img_prompt)
dummy_component = gr.Label(visible=False)
@@ -335,6 +367,9 @@ def create_ui():
with gr.Tab("Generation", id="txt2img_generation") as txt2img_generation_tab, gr.Row(equal_height=False):
with gr.Column(variant='compact', elem_id="txt2img_settings"):
+ if opts.move_toprow_to_settings_column:
+ toprow = Toprow(is_img2img=False, toprow_prompt=txt2img_prompt, in_settings_column=True)
+
scripts.scripts_txt2img.prepare_ui()
for category in ordered_ui_categories():
@@ -543,14 +578,19 @@ def create_ui():
scripts.scripts_current = scripts.scripts_img2img
scripts.scripts_img2img.initialize_scripts(is_img2img=True)
+ img2img_prompt = ToprowPrompt("img2img")
with gr.Blocks(analytics_enabled=False) as img2img_interface:
- toprow = Toprow(is_img2img=True)
+ if not opts.move_toprow_to_settings_column:
+ toprow = Toprow(is_img2img=True, toprow_prompt=img2img_prompt)
extra_tabs = gr.Tabs(elem_id="img2img_extra_tabs")
extra_tabs.__enter__()
with gr.Tab("Generation", id="img2img_generation") as img2img_generation_tab, FormRow(equal_height=False):
with gr.Column(variant='compact', elem_id="img2img_settings"):
+ if opts.move_toprow_to_settings_column:
+ toprow = Toprow(is_img2img=True, toprow_prompt=img2img_prompt, in_settings_column=True)
+
copy_image_buttons = []
copy_image_destinations = {}
diff --git a/modules/ui_prompt_styles.py b/modules/ui_prompt_styles.py
index 85eb3a6417e..e911ca08108 100644
--- a/modules/ui_prompt_styles.py
+++ b/modules/ui_prompt_styles.py
@@ -50,12 +50,14 @@ def refresh_styles():
class UiPromptStyles:
- def __init__(self, tabname, main_ui_prompt, main_ui_negative_prompt):
+ def __init__(self, tabname, main_ui_prompt, main_ui_negative_prompt, extra_buttons=None):
self.tabname = tabname
with gr.Row(elem_id=f"{tabname}_styles_row"):
self.dropdown = gr.Dropdown(label="Styles", show_label=False, elem_id=f"{tabname}_styles", choices=list(shared.prompt_styles.styles), value=[], multiselect=True, tooltip="Styles")
edit_button = ui_components.ToolButton(value=styles_edit_symbol, elem_id=f"{tabname}_styles_edit_button", tooltip="Edit styles")
+ if extra_buttons is not None:
+ extra_buttons()
with gr.Box(elem_id=f"{tabname}_styles_dialog", elem_classes="popup-dialog") as styles_dialog:
with gr.Row():
diff --git a/style.css b/style.css
index 38a01e72d39..6f9100707a6 100644
--- a/style.css
+++ b/style.css
@@ -142,6 +142,10 @@ div.gradio-container, .block.gradio-textbox, div.gradio-group, div.gradio-dropdo
overflow: visible !important;
}
+.flex-basis-revert {
+ flex-basis: revert !important;
+}
+
/* general styled components */
@@ -322,6 +326,12 @@ div.block.gradio-accordion {
right: 0;
border-radius: 0 0.5rem 0.5rem 0;
}
+.generate-sticky{
+ position: sticky;
+ z-index: 500;
+ top: 0.5em;
+ margin-bottom: 1em;
+}
#img2img_scale_resolution_preview.block{
display: flex;