This commit is contained in:
Alejandro Gil 2023-01-07 14:18:56 -08:00 committed by GitHub
commit 571fb897ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 3070 additions and 1392 deletions

5
.gitignore vendored
View File

@ -59,14 +59,17 @@ condaenv.*.requirements.txt
!/src/components/*
!/src/pages/*
/src/*
/inputs
/outputs
/model_cache
/log/**/*.png
/log/webui/*
/log/log.csv
/flagged/*
/gfpgan/*
/models/*
/webui/flet/assets/uploads/
/webui/flet/assets/outputs/
z_version_env.tmp
scripts/bridgeData.py
/user_data/*

View File

@ -33,6 +33,7 @@
# -option_list
# slider
# -value
# -value_type 'int', 'float'
# -min
# -max
# -step
@ -56,15 +57,20 @@ webui_page:
default_text_size:
display: slider
value: !!int '20'
value_type: 'int'
min: !!int '10'
max: !!int '32'
step: !!float '2.0'
max_message_history:
display: slider
value: !!int '20'
value_type: 'int'
min: !!int '1'
max: !!int '100'
step: !!int '1'
theme_color:
display: textinput
value: !!str 'blue'
general_page:
huggingface_token:
@ -176,7 +182,9 @@ performance_page:
keep_all_models_loaded:
display: bool
value: !!bool 'false'
#no_verify_input: False
no_verify_input:
display: bool
value: !!bool 'false'
#no_half: False
#use_float16: False
#precision: "autocast"

View File

@ -56,6 +56,8 @@ general:
grid_quality: 95
n_rows: -1
no_verify_input: False
show_percent_in_tab_title: True
enable_suggestions: True
no_half: False
use_float16: False
precision: "autocast"
@ -427,3 +429,11 @@ model_manager:
ldsr_model:
file_name: "model.ckpt"
download_link: "https://heibox.uni-heidelberg.de/f/578df07c8fc04ffbadf3/?dl=1"
sygil:
model_name: "Sygil Diffusion 0.1"
save_location: "./models/custom"
files:
sd_wd_ld_trinart_merged:
file_name: "sygil-diffusion-v0.1_365608_lora.ckpt"
download_link: "https://huggingface.co/Sygil/Sygil-Diffusion-v0.1/resolve/main/sygil-diffusion-v0.1_365608_lora.ckpt"

View File

@ -208,6 +208,14 @@ def layout():
st.session_state["defaults"].general.no_verify_input = st.checkbox("Do not Verify Input", value=st.session_state['defaults'].general.no_verify_input,
help="Do not verify input to check if it's too long. Default: False")
st.session_state["defaults"].general.show_percent_in_tab_title = st.checkbox("Show Percent in tab title", value=st.session_state['defaults'].general.show_percent_in_tab_title,
help="Add the progress percent value to the page title on the tab on your browser. "
"This is useful in case you need to know how the generation is going while doign something else"
"in another tab on your browser. Default: True")
st.session_state["defaults"].general.enable_suggestions = st.checkbox("Enable Suggestions Box", value=st.session_state['defaults'].general.enable_suggestions,
help="Adds a suggestion box under the prompt when clicked. Default: True")
st.session_state["defaults"].daisi_app.running_on_daisi_io = st.checkbox("Running on Daisi.io?", value=st.session_state['defaults'].daisi_app.running_on_daisi_io,
help="Specify if we are running on app.Daisi.io . Default: False")

View File

@ -18,7 +18,7 @@ from sd_utils import st, server_state, no_rerun, \
generation_callback, process_images, KDiffusionSampler, \
custom_models_available, RealESRGAN_available, GFPGAN_available, \
LDSR_available, load_models, hc, seed_to_int, logger, \
resize_image, get_matched_noise, CFGMaskedDenoiser, ImageFilter
resize_image, get_matched_noise, CFGMaskedDenoiser, ImageFilter, set_page_title
# streamlit imports
from streamlit.runtime.scriptrunner import StopException
@ -82,6 +82,8 @@ def img2img(prompt: str = '', init_info: any = None, init_info_mask: any = None,
sampler = KDiffusionSampler(server_state["model"],'dpm_2_ancestral')
elif sampler_name == 'k_dpm_2':
sampler = KDiffusionSampler(server_state["model"],'dpm_2')
elif sampler_name == 'k_dpmpp_2m':
sampler = KDiffusionSampler(server_state["model"],'dpmpp_2m')
elif sampler_name == 'k_euler_a':
sampler = KDiffusionSampler(server_state["model"],'euler_ancestral')
elif sampler_name == 'k_euler':
@ -377,7 +379,10 @@ def layout():
#prompt = st.text_area("Input Text","")
placeholder = "A corgi wearing a top hat as an oil painting."
prompt = st.text_area("Input Text","", placeholder=placeholder, height=54)
sygil_suggestions.suggestion_area(placeholder)
if "defaults" in st.session_state:
if st.session_state["defaults"].general.enable_suggestions:
sygil_suggestions.suggestion_area(placeholder)
if "defaults" in st.session_state:
if st.session_state['defaults'].admin.global_negative_prompt:
@ -411,7 +416,7 @@ def layout():
min_value=st.session_state['defaults'].img2img.sampling_steps.min_value,
step=st.session_state['defaults'].img2img.sampling_steps.step)
sampler_name_list = ["k_lms", "k_euler", "k_euler_a", "k_dpm_2", "k_dpm_2_a", "k_heun", "PLMS", "DDIM"]
sampler_name_list = ["k_lms", "k_euler", "k_euler_a", "k_dpm_2", "k_dpm_2_a", "k_dpmpp_2m", "k_heun", "PLMS", "DDIM"]
st.session_state["sampler_name"] = st.selectbox("Sampling method",sampler_name_list,
index=sampler_name_list.index(st.session_state['defaults'].img2img.sampler_name), help="Sampling method to use.")
@ -733,8 +738,12 @@ def layout():
#show a message when the generation is complete.
message.success('Render Complete: ' + info + '; Stats: ' + stats, icon="")
except (StopException, KeyError):
except (StopException,
#KeyError
):
logger.info(f"Received Streamlit StopException")
# reset the page title so the percent doesnt stay on it confusing the user.
set_page_title(f"Stable Diffusion Playground")
# this will render all the images at the end of the generation but its better if its moved to a second tab inside col2 and shown as a gallery.
# use the current col2 first tab to show the preview_img and update it as its generated.

View File

@ -109,6 +109,9 @@ try:
except:
pass
# disable diffusers telemetry
os.environ["DISABLE_TELEMETRY"] = "YES"
# remove some annoying deprecation warnings that show every now and then.
warnings.filterwarnings("ignore", category=DeprecationWarning)
warnings.filterwarnings("ignore", category=UserWarning)
@ -941,7 +944,7 @@ class LDSR():
global_step = pl_sd["global_step"]
sd = pl_sd["state_dict"]
config = OmegaConf.load(self.yamlPath)
model = instantiate_from_config(config.model)
model = instantiate_from_config(config.model, personalization_config="")
m, u = model.load_state_dict(sd, strict=False)
model.cuda()
model.eval()
@ -1692,6 +1695,8 @@ def generation_callback(img, i=0):
if "progress_bar" in st.session_state:
try:
st.session_state["progress_bar"].progress(percent if percent < 100 else 100)
if st.session_state["defaults"].general.show_percent_in_tab_title:
set_page_title(f"({percent if percent < 100 else 100}%) Stable Diffusion Playground")
except UnboundLocalError as e:
#logger.error(e)
pass

View File

@ -19,7 +19,7 @@ from sd_utils import st, MemUsageMonitor, server_state, no_rerun, \
save_sample, generation_callback, process_images, \
KDiffusionSampler, \
custom_models_available, RealESRGAN_available, GFPGAN_available, \
LDSR_available, load_models, hc, seed_to_int, logger
LDSR_available, load_models, hc, seed_to_int, logger, set_page_title
# streamlit imports
from streamlit.runtime.scriptrunner import StopException
@ -316,6 +316,8 @@ def txt2img(prompt: str, ddim_steps: int, sampler_name: str, n_iter: int, batch_
sampler = KDiffusionSampler(server_state["model"],'dpm_2_ancestral')
elif sampler_name == 'k_dpm_2':
sampler = KDiffusionSampler(server_state["model"],'dpm_2')
elif sampler_name == 'k_dpmpp_2m':
sampler = KDiffusionSampler(server_state["model"],'dpmpp_2m')
elif sampler_name == 'k_euler_a':
sampler = KDiffusionSampler(server_state["model"],'euler_ancestral')
elif sampler_name == 'k_euler':
@ -425,7 +427,10 @@ def layout():
#prompt = st.text_area("Input Text","")
placeholder = "A corgi wearing a top hat as an oil painting."
prompt = st.text_area("Input Text","", placeholder=placeholder, height=54)
sygil_suggestions.suggestion_area(placeholder)
if "defaults" in st.session_state:
if st.session_state["defaults"].general.enable_suggestions:
sygil_suggestions.suggestion_area(placeholder)
if "defaults" in st.session_state:
if st.session_state['defaults'].admin.global_negative_prompt:
@ -519,7 +524,7 @@ def layout():
step=st.session_state['defaults'].txt2img.sampling_steps.step,
help="Set the default number of sampling steps to use. Default is: 30 (with k_euler)")
sampler_name_list = ["k_lms", "k_euler", "k_euler_a", "k_dpm_2", "k_dpm_2_a", "k_heun", "PLMS", "DDIM"]
sampler_name_list = ["k_lms", "k_euler", "k_euler_a", "k_dpm_2", "k_dpm_2_a", "k_dpmpp_2m", "k_heun", "PLMS", "DDIM"]
sampler_name = st.selectbox("Sampling method", sampler_name_list,
index=sampler_name_list.index(st.session_state['defaults'].txt2img.default_sampler), help="Sampling method to use. Default: k_euler")
@ -668,30 +673,35 @@ def layout():
#print(st.session_state['use_RealESRGAN'])
#print(st.session_state['use_LDSR'])
#try:
#
try:
output_images, seeds, info, stats = txt2img(prompt, st.session_state.sampling_steps, sampler_name, st.session_state["batch_count"], st.session_state["batch_size"],
cfg_scale, seed, height, width, separate_prompts, normalize_prompt_weights, save_individual_images,
save_grid, group_by_prompt, save_as_jpg, st.session_state["use_GFPGAN"], st.session_state['GFPGAN_model'],
use_RealESRGAN=st.session_state["use_RealESRGAN"], RealESRGAN_model=st.session_state["RealESRGAN_model"],
use_LDSR=st.session_state["use_LDSR"], LDSR_model=st.session_state["LDSR_model"],
variant_amount=variant_amount, variant_seed=variant_seed, write_info_files=write_info_files,
use_stable_horde=use_stable_horde, stable_horde_key=stable_horde_key)
output_images, seeds, info, stats = txt2img(prompt, st.session_state.sampling_steps, sampler_name, st.session_state["batch_count"], st.session_state["batch_size"],
cfg_scale, seed, height, width, separate_prompts, normalize_prompt_weights, save_individual_images,
save_grid, group_by_prompt, save_as_jpg, st.session_state["use_GFPGAN"], st.session_state['GFPGAN_model'],
use_RealESRGAN=st.session_state["use_RealESRGAN"], RealESRGAN_model=st.session_state["RealESRGAN_model"],
use_LDSR=st.session_state["use_LDSR"], LDSR_model=st.session_state["LDSR_model"],
variant_amount=variant_amount, variant_seed=variant_seed, write_info_files=write_info_files,
use_stable_horde=use_stable_horde, stable_horde_key=stable_horde_key)
message.success('Render Complete: ' + info + '; Stats: ' + stats, icon="")
with gallery_tab:
logger.info(seeds)
st.session_state["gallery"].text = ""
sdGallery(output_images)
message.success('Render Complete: ' + info + '; Stats: ' + stats, icon="")
with gallery_tab:
logger.info(seeds)
st.session_state["gallery"].text = ""
sdGallery(output_images)
#except (StopException, KeyError):
#print(f"Received Streamlit StopException")
# this will render all the images at the end of the generation but its better if its moved to a second tab inside col2 and shown as a gallery.
# use the current col2 first tab to show the preview_img and update it as its generated.
#preview_image.image(output_images)
except (StopException,
#KeyError
):
print(f"Received Streamlit StopException")
# reset the page title so the percent doesnt stay on it confusing the user.
set_page_title(f"Stable Diffusion Playground")
# this will render all the images at the end of the generation but its better if its moved to a second tab inside col2 and shown as a gallery.
# use the current col2 first tab to show the preview_img and update it as its generated.
#preview_image.image(output_images)

View File

@ -24,7 +24,7 @@ https://gist.github.com/karpathy/00103b0037c5aaea32fe1da1af553355
from sd_utils import st, MemUsageMonitor, server_state, no_rerun, torch_gc, \
custom_models_available, RealESRGAN_available, GFPGAN_available, \
LDSR_available, hc, seed_to_int, logger, slerp, optimize_update_preview_frequency, \
load_learned_embed_in_clip, load_GFPGAN, RealESRGANModel
load_learned_embed_in_clip, load_GFPGAN, RealESRGANModel, set_page_title
# streamlit imports
@ -942,12 +942,12 @@ class StableDiffusionWalkPipeline(DiffusionPipeline):
def diffuse(
pipe,
cond_embeddings, # text conditioning, should be (1, 77, 768)
cond_latents, # image conditioning, should be (1, 4, 64, 64)
num_inference_steps,
cfg_scale,
eta,
fps=30
):
cond_latents, # image conditioning, should be (1, 4, 64, 64)
num_inference_steps,
cfg_scale,
eta,
fps=30
):
torch_device = cond_latents.get_device()
@ -1082,6 +1082,9 @@ def diffuse(
if "progress_bar" in st.session_state:
st.session_state["progress_bar"].progress(total_percent if total_percent < 100 else 100)
if st.session_state["defaults"].general.show_percent_in_tab_title:
set_page_title(f"({percent if percent < 100 else 100}%) Stable Diffusion Playground")
except KeyError:
raise StopException
@ -1130,7 +1133,7 @@ def load_diffusers_model(weights_path,torch_device):
if not os.path.exists(model_path + "/model_index.json"):
server_state["pipe"] = StableDiffusionPipeline.from_pretrained(
weights_path,
use_local_file=True,
#use_local_file=True,
use_auth_token=st.session_state["defaults"].general.huggingface_token,
torch_dtype=torch.float16 if st.session_state['defaults'].general.use_float16 else None,
revision="fp16" if not st.session_state['defaults'].general.no_half else None,
@ -1143,7 +1146,7 @@ def load_diffusers_model(weights_path,torch_device):
else:
server_state["pipe"] = StableDiffusionPipeline.from_pretrained(
model_path,
use_local_file=True,
#use_local_file=True,
torch_dtype=torch.float16 if st.session_state['defaults'].general.use_float16 else None,
revision="fp16" if not st.session_state['defaults'].general.no_half else None,
safety_checker=None, # Very important for videos...lots of false positives while interpolating
@ -1178,7 +1181,8 @@ def load_diffusers_model(weights_path,torch_device):
server_state['float16'] = st.session_state['defaults'].general.use_float16
server_state['no_half'] = st.session_state['defaults'].general.no_half
server_state['optimized'] = st.session_state['defaults'].general.optimized
#with no_rerun:
load_diffusers_model(weights_path, torch_device)
else:
logger.info("Tx2Vid Model already Loaded")
@ -1318,28 +1322,28 @@ def txt2vid(
with open(os.path.join(full_path , f'{slugify(str(seeds))}_config.json' if len(prompts) > 1 else "prompts_config.json"), "w") as outfile:
outfile.write(json.dumps(
dict(
prompts = prompts,
gpu = gpu,
num_steps = num_steps,
max_duration_in_seconds = max_duration_in_seconds,
num_inference_steps = num_inference_steps,
cfg_scale = cfg_scale,
do_loop = do_loop,
use_lerp_for_text = use_lerp_for_text,
seeds = seeds,
quality = quality,
eta = eta,
width = width,
height = height,
weights_path = weights_path,
scheduler=scheduler,
disable_tqdm = disable_tqdm,
beta_start = beta_start,
beta_end = beta_end,
beta_schedule = beta_schedule
),
indent=2,
sort_keys=False,
prompts = prompts,
gpu = gpu,
num_steps = num_steps,
max_duration_in_seconds = max_duration_in_seconds,
num_inference_steps = num_inference_steps,
cfg_scale = cfg_scale,
do_loop = do_loop,
use_lerp_for_text = use_lerp_for_text,
seeds = seeds,
quality = quality,
eta = eta,
width = width,
height = height,
weights_path = weights_path,
scheduler=scheduler,
disable_tqdm = disable_tqdm,
beta_start = beta_start,
beta_end = beta_end,
beta_schedule = beta_schedule
),
indent=2,
sort_keys=False,
))
#print(scheduler)
@ -1383,10 +1387,11 @@ def txt2vid(
#flaxddpms=flaxddpms_scheduler,
#flaxpndms=flaxpndms_scheduler,
)
with st.session_state["progress_bar_text"].container():
with hc.HyLoader('Loading Models...', hc.Loaders.standard_loaders,index=[0]):
load_diffusers_model(weights_path, torch_device)
with no_rerun:
with st.session_state["progress_bar_text"].container():
with hc.HyLoader('Loading Models...', hc.Loaders.standard_loaders,index=[0]):
load_diffusers_model(weights_path, torch_device)
if "pipe" not in server_state:
logger.error('wtf')
@ -1593,6 +1598,9 @@ def txt2vid(
video_path = save_video_to_disk(frames, seeds, sanitized_prompt, save_video=save_video, outdir=outdir)
except StopException:
# reset the page title so the percent doesnt stay on it confusing the user.
set_page_title(f"Stable Diffusion Playground")
if save_video_on_stop:
logger.info("Streamlit Stop Exception Received. Saving video")
video_path = save_video_to_disk(frames, seeds, sanitized_prompt, save_video=save_video, outdir=outdir)
@ -1626,7 +1634,10 @@ def layout():
#prompt = st.text_area("Input Text","")
placeholder = "A corgi wearing a top hat as an oil painting."
prompt = st.text_area("Input Text","", placeholder=placeholder, height=54)
sygil_suggestions.suggestion_area(placeholder)
if "defaults" in st.session_state:
if st.session_state["defaults"].general.enable_suggestions:
sygil_suggestions.suggestion_area(placeholder)
if "defaults" in st.session_state:
if st.session_state['defaults'].admin.global_negative_prompt:
@ -1909,25 +1920,25 @@ def layout():
#print("Loading models")
# load the models when we hit the generate button for the first time, it wont be loaded after that so dont worry.
#load_models(False, st.session_state["use_GFPGAN"], True, st.session_state["RealESRGAN_model"])
with no_rerun:
if st.session_state["use_GFPGAN"]:
if "GFPGAN" in server_state:
logger.info("GFPGAN already loaded")
else:
with col2:
with hc.HyLoader('Loading Models...', hc.Loaders.standard_loaders,index=[0]):
# Load GFPGAN
if os.path.exists(st.session_state["defaults"].general.GFPGAN_dir):
try:
load_GFPGAN()
logger.info("Loaded GFPGAN")
except Exception:
import traceback
logger.error("Error loading GFPGAN:", file=sys.stderr)
logger.error(traceback.format_exc(), file=sys.stderr)
#with no_rerun:
if st.session_state["use_GFPGAN"]:
if "GFPGAN" in server_state:
logger.info("GFPGAN already loaded")
else:
if "GFPGAN" in server_state:
del server_state["GFPGAN"]
with col2:
with hc.HyLoader('Loading Models...', hc.Loaders.standard_loaders,index=[0]):
# Load GFPGAN
if os.path.exists(st.session_state["defaults"].general.GFPGAN_dir):
try:
load_GFPGAN()
logger.info("Loaded GFPGAN")
except Exception:
import traceback
logger.error("Error loading GFPGAN:", file=sys.stderr)
logger.error(traceback.format_exc(), file=sys.stderr)
else:
if "GFPGAN" in server_state:
del server_state["GFPGAN"]
#try:
# run video generation

File diff suppressed because it is too large Load Diff

View File

@ -1,65 +0,0 @@
# webui_utils.py
# imports
import os, yaml
from PIL import Image
from pprint import pprint
# logging
log_file = 'webui_flet.log'
def log_message(message):
with open(log_file,'a+') as log:
log.write(message)
# Settings
path_to_default_config = 'configs/webui/webui_flet.yaml'
path_to_user_config = 'configs/webui/userconfig_flet.yaml'
def get_default_settings_from_config():
with open(path_to_default_config) as f:
default_settings = yaml.safe_load(f)
return default_settings
def get_user_settings_from_config():
settings = get_default_settings_from_config()
if os.path.exists(path_to_user_config):
with open(path_to_user_config) as f:
user_settings = yaml.safe_load(f)
settings.update(user_settings)
return settings
def save_user_settings_to_config(settings):
with open(path_to_user_config, 'w+') as f:
yaml.dump(settings, f, default_flow_style=False)
# Image handling
def load_images(images): # just for testing, needs love to function
images_loaded = {}
images_not_loaded = []
for i in images:
try:
img = Image.open(images[i]['path'])
if img:
images_loaded.update({images[i].name:img})
except:
images_not_loaded.append(i)
return images_loaded, images_not_loaded
def create_blank_image():
img = Image.new('RGBA',(512,512),(0,0,0,0))
return img
# Textual Inversion
textual_inversion_grid_row_list = [
'model', 'medium', 'artist', 'trending', 'movement', 'flavors', 'techniques', 'tags',
]
def run_textual_inversion(args):
pass

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,35 @@
{
"name": "Sygil",
"short_name": "Sygil",
"start_url": ".",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#5175C2",
"description": "This is a customized Flet app description.",
"orientation": "natural",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

View File

@ -0,0 +1,4 @@
# other imports
from math import pi
from typing import Optional
from loguru import logger

View File

@ -0,0 +1,430 @@
# flet_layer_manager.py
# Flet imports
import flet as ft
# utils imports
from scripts import flet_utils
class AssetManager(ft.Container):
def setup(self):
self.width = self.page.left_panel_width
self.bgcolor = self.page.primary_color
self.padding = self.page.container_padding
self.margin = self.page.container_margin
self.set_tab_text_size(self.page.text_size)
self.set_tab_bgcolor(self.page.secondary_color)
self.set_tab_padding(self.page.container_padding)
self.set_tab_margin(self.page.container_margin)
self.dragbar.content.width = self.page.vertical_divider_width
self.dragbar.content.color = self.page.tertiary_color
def on_page_change(self):
self.width = self.page.left_panel_width
self.bgcolor = self.page.primary_color
self.padding = self.page.container_padding
self.margin = self.page.container_margin
self.set_tab_text_size(self.page.text_size)
self.set_tab_bgcolor(self.page.secondary_color)
self.set_tab_padding(self.page.container_padding)
self.set_tab_margin(self.page.container_margin)
self.dragbar.content.width = self.page.vertical_divider_width
self.dragbar.content.color = self.page.tertiary_color
if self.page.active_layer is not None:
self.page.active_layer.handle.color = self.page.tertiary_color
def add_image_as_layer(self, image):
return self.layer_panel.add_image_as_layer(image)
def add_images_as_layers(self, images):
return self.layer_panel.add_images_as_layers(images)
def set_tab_text_size(self, size):
for tab in self.tabs:
tab.tab_content.size = size
def set_tab_bgcolor(self, color):
for tab in self.tabs:
tab.content.bgcolor = color
def set_tab_padding(self, padding):
for tab in self.tabs:
tab.content.padding = padding
def set_tab_margin(self, margin):
for tab in self.tabs:
tab.content.margin = margin
def resize_asset_manager(self, e: ft.DragUpdateEvent):
self.page.left_panel_width = max(250, self.page.left_panel_width + e.delta_x)
self.width = self.page.left_panel_width
self.page.update()
def refresh_layers(self):
self.layer_panel.refresh_layers()
class AssetPanel(ft.Container):
pass
class LayerPanel(ft.Container):
def refresh_layers(self):
self.layers = self.content.content.controls
self.refresh_layer_indexes()
self.refresh_visible_layers()
self.update()
def refresh_layer_indexes(self):
count = 0
for layer in self.layers:
layer.index = count
count += 1
def refresh_visible_layers(self):
self.page.visible_layers = []
for layer in self.layers:
if not layer.disabled:
self.page.visible_layers.append(layer)
def refresh_layer_name(self, e):
self.page.refresh_layers()
def make_layer_active(self, index):
for i, layer in enumerate(self.layers):
layer.active = False
layer.handle.color = None
if i == index:
layer.active = True
layer.handle.color = self.page.tertiary_color
self.page.set_active_layer(layer)
def add_layer_slot(self, image):
label = ft.TextField(
value = image.filename,
focused_border_color = self.page.tertiary_color,
text_size = self.page.text_size,
content_padding = ft.padding.only(left = 12, top = 0, right = 0, bottom = 0),
expand = True,
on_submit = self.refresh_layer_name,
)
handle = ft.Icon(
name = ft.icons.DRAG_HANDLE,
color = None,
tooltip = 'drag to move',
)
layer_slot = LayerSlot(
content = ft.Row(
controls = [
label,
handle,
],
expand = True,
),
height = self.page.layer_height,
padding = 0,
margin = 0,
)
layer_slot.label = label
layer_slot.handle = handle
layer_slot.index = 0
layer_slot.disabled = False
layer_slot.active = False
layer_slot.image = image
layer_slot.layer_image = None
self.content.content.controls.insert(0,layer_slot)
self.layers = self.content.content.controls
self.refresh_layer_indexes()
self.make_layer_active(0)
return layer_slot
def add_image_as_layer(self, image):
return self.add_layer_slot(image)
def add_images_as_layers(self, images):
layer_slots = []
for image in images:
layer_slots.append(self.add_image_as_layer(image))
return layer_slots
def get_layer_index_from_position(self, pos):
index = int(pos / self.page.layer_height)
return index
def move_layer(self, layer, index):
if index > len(self.layers):
layer = self.layers.pop(layer.index)
self.layers.append(layer)
if layer.index < index:
index -= 1
layer = self.layers.pop(layer.index)
self.layers.insert(index, layer)
self.page.refresh_layers()
def delete_layer(self, layer):
if not layer:
return
self.layers.pop(layer.index)
class LayerSlot(ft.Container):
pass
class LayerActionMenu(ft.Card):
def show_menu(self):
self.visible = True
self.update()
def hide_menu(self):
self.visible = False
self.update()
def close_menu(e):
layer_action_menu.hide_menu()
def show_hide_layer(e):
e.page.active_layer.disabled = False if e.page.active_layer.disabled else True
e.page.refresh_layers()
close_menu(e)
def move_layer_to_top(e):
layer_panel.move_layer(e.page.active_layer, 0)
close_menu(e)
def move_layer_up(e):
layer_panel.move_layer(e.page.active_layer, e.page.active_layer.index - 1)
close_menu(e)
def move_layer_down(e):
layer_panel.move_layer(e.page.active_layer, e.page.active_layer.index + 2)
close_menu(e)
def delete_layer(e):
layer_panel.delete_layer(e.page.active_layer)
e.page.active_layer = None
e.page.refresh_layers()
close_menu(e)
class LayerAction():
def __init__(self, text, on_click):
self.text = text
self.on_click = on_click
layer_action_list = [
LayerAction('Show/Hide Layer', show_hide_layer),
LayerAction('Move Layer To Top', move_layer_to_top),
LayerAction('Move Layer Up', move_layer_up),
LayerAction('Move Layer Down', move_layer_down),
LayerAction('Delete Layer', delete_layer),
]
def make_action_buttons(action_list):
button_list = []
for action in action_list:
button_list.append(
ft.TextButton(
text = action.text,
on_click = action.on_click,
)
)
return button_list
# LayerActionMenu == ft.Card
layer_action_menu = LayerActionMenu(
content = ft.GestureDetector(
content = ft.Column(
controls = make_action_buttons(layer_action_list),
expand = False,
spacing = 0,
alignment = 'start',
tight = True,
),
on_exit = close_menu,
),
margin = 0,
visible = False,
)
def layer_left_click(e: ft.TapEvent):
index = layer_panel.get_layer_index_from_position(e.local_y)
if index >= len(layer_panel.layers):
return
layer_panel.make_layer_active(index)
layer_panel.update()
def layer_right_click(e: ft.TapEvent):
index = layer_panel.get_layer_index_from_position(e.local_y)
if index >= len(layer_panel.layers):
return
layer_panel.make_layer_active(index)
layer_panel.update()
layer_action_menu.left = e.global_x
layer_action_menu.top = e.global_y
layer_action_menu.show_menu()
def pickup_layer(e: ft.DragStartEvent):
index = layer_panel.get_layer_index_from_position(e.local_y)
if index >= len(layer_panel.layers):
return
layer_panel.layer_being_moved = layer_panel.layers[index]
layer_panel.make_layer_active(layer_panel.layer_being_moved.index)
layer_panel.update()
def on_layer_drag(e: ft.DragUpdateEvent):
if not layer_panel.layer_being_moved:
return
index = layer_panel.get_layer_index_from_position(e.local_y)
if index == layer_panel.layer_being_moved.index:
return
layer_panel.move_layer(layer_panel.layer_being_moved, index)
def drop_layer(e: ft.DragEndEvent):
e.page.refresh_layers()
layer_panel.layer_being_moved = None
# LayerPanel == ft.Container
layer_panel = LayerPanel(
content = ft.GestureDetector(
content = ft.Column(
controls = [],
alignment = 'start',
expand = True,
spacing = 0,
scroll = 'hidden',
),
drag_interval = 10,
on_tap_down = layer_left_click,
on_secondary_tap_down = layer_right_click,
on_vertical_drag_start = pickup_layer,
on_vertical_drag_update = on_layer_drag,
on_vertical_drag_end = drop_layer,
),
)
layer_panel.layers = []
layer_panel.layer_being_moved = None
layer_panel.layer_last_index = 0
# AssetPanel == ft.Container
asset_panel = AssetPanel(
content = ft.Column(
controls = [
ft.Text("Under Construction"),
],
),
)
def resize_asset_manager(e):
asset_manager.resize_asset_manager(e)
def realign_canvas(e):
e.page.align_canvas()
asset_manager_dragbar = ft.GestureDetector(
mouse_cursor = ft.MouseCursor.RESIZE_COLUMN,
drag_interval = 50,
on_pan_update = resize_asset_manager,
on_pan_end = realign_canvas,
content = ft.VerticalDivider(),
)
# AssetManager == ft.Container
asset_manager = AssetManager(
content = ft.Row(
controls = [
ft.Column(
controls = [
ft.Tabs(
selected_index = 0,
animation_duration = 300,
tabs = [
ft.Tab(
content = layer_panel,
tab_content = ft.Text(
value = "Layers",
),
),
ft.Tab(
content = asset_panel,
tab_content = ft.Text(
value = "Assets",
),
),
],
),
],
alignment = 'start',
expand = True
),
asset_manager_dragbar,
],
expand = True,
),
clip_behavior = 'antiAlias',
)
asset_manager.tabs = asset_manager.content.controls[0].controls[0].tabs
asset_manager.layer_panel = layer_panel
asset_manager.asset_panel = asset_panel
asset_manager.dragbar = asset_manager_dragbar
'''
# keep track of which layers are visible
def show_hide_layer(self, e):
parent = e.control.data['parent']
if parent.data['visible']:
parent.data['visible'] = False
parent.opacity = 0.5
e.control.icon = ft.icons.VISIBILITY_OFF
else:
parent.data['visible'] = True
parent.opacity = 1.0
e.control.icon = ft.icons.VISIBILITY
self.update_visible_layer_list()
parent.update()
self.page.refresh_canvas()
def update_visible_layer_list(self):
self.page.visible_layer_list = []
layer_list = self.page.layer_list
for layer in layer_list:
if layer.data['type'] == 'slot':
if layer.content.content.controls[1].data['visible']:
self.page.visible_layer_list.append(layer)
# keep track of which layers are active
def lock_unlock_layer(self, e):
parent = e.control.data['parent']
if parent.data['locked']:
parent.data['locked'] = False
e.control.icon = ft.icons.LOCK_OPEN_OUTLINED
else:
parent.data['locked'] = True
e.control.icon = ft.icons.LOCK_OUTLINED
self.update_active_layer_list()
parent.update()
def update_active_layer_list(self):
self.page.active_layer_list = []
layer_list = self.page.layer_list
for layer in layer_list:
if layer.data['type'] == 'slot':
if not layer.content.content.controls[1].data['locked']:
self.page.active_layer_list.append(layer)
'''

View File

@ -0,0 +1,446 @@
# flet_canvas.py
# Flet imports
import flet as ft
# utils imports
from scripts import flet_utils
class Canvas(ft.Container):
def setup(self):
self.bgcolor = self.page.secondary_color
self.padding = self.page.container_padding
self.margin = self.page.container_margin
self.overlay.tools.center.icon_size = self.page.icon_size
self.overlay.tools.zoom_in.icon_size = self.page.icon_size
self.overlay.tools.zoom_out.icon_size = self.page.icon_size
self.overlay.size_display.content.color = self.page.text_color
self.overlay.size_display.content.size = self.page.text_size
self.add_canvas_background()
self.center_canvas()
self.refresh_canvas()
def on_page_change(self):
self.bgcolor = self.page.secondary_color
self.padding = self.page.container_padding
self.margin = self.page.container_margin
self.overlay.tools.center.icon_size = self.page.icon_size
self.overlay.tools.zoom_in.icon_size = self.page.icon_size
self.overlay.tools.zoom_out.icon_size = self.page.icon_size
self.overlay.size_display.content.color = self.page.text_color
self.overlay.size_display.content.size = self.page.text_size
self.refresh_canvas()
def refresh_canvas(self):
self.image_stack.refresh_stack()
self.align_canvas()
self.overlay.refresh_canvas_overlay()
def set_current_tool(self, tool):
self.page.current_tool = tool
def add_canvas_background(self):
self.image_stack.add_canvas_background()
def add_layer_image(self, image):
return self.image_stack.add_layer_image(image)
def get_image_stack_preview(self):
return self.image_stack.get_preview()
def center_canvas(self):
width, height = self.page.get_viewport_size()
self.image_stack.offset_x = 0
self.image_stack.offset_y = 0
self.image_stack.left = (width * 0.5) - (self.image_stack.width * 0.5)
self.image_stack.top = (height * 0.5) - (self.image_stack.height * 0.5)
self.overlay.frame.left = self.image_stack.left
self.overlay.frame.top = self.image_stack.top
self.update()
def align_canvas(self):
width, height = self.page.get_viewport_size()
self.image_stack.left = (width * 0.5) - (self.image_stack.width * 0.5) + self.image_stack.offset_x
self.image_stack.top = (height * 0.5) - (self.image_stack.height * 0.5) + self.image_stack.offset_y
self.overlay.frame.left = self.image_stack.left
self.overlay.frame.top = self.image_stack.top
self.overlay.frame.scale = self.image_stack.scale
self.update()
def pan_canvas(self, e: ft.DragUpdateEvent):
self.image_stack.offset_x += e.delta_x
self.image_stack.offset_y += e.delta_y
width, height = self.page.get_viewport_size()
self.image_stack.offset_x = max(self.image_stack.offset_x, (width - self.image_stack.width) * 0.5)
self.image_stack.offset_y = max(self.image_stack.offset_y, (height - self.image_stack.height) * 0.5)
self.image_stack.offset_x = min(self.image_stack.offset_x, (self.image_stack.width - width) * 0.5)
self.image_stack.offset_y = min(self.image_stack.offset_y, (self.image_stack.height - height) * 0.5)
self.align_canvas()
def zoom_in(self, e):
if self.image_stack.scale >= 4.0:
self.image_stack.scale = 4.0
else:
self.image_stack.scale += 0.05
self.image_stack.get_scaled_size()
self.overlay.frame.scale = self.image_stack.scale
self.align_canvas()
def zoom_out(self, e):
if self.image_stack.scale <= 0.1:
self.image_stack.scale = 0.1
else:
self.image_stack.scale -= 0.05
self.overlay.frame.scale = self.image_stack.scale
self.image_stack.get_scaled_size()
self.align_canvas()
def clear_tools(self):
self.overlay.clear_tools()
def set_current_tool(self, tool):
if tool == 'pan':
self.overlay.controls.pop(2)
self.overlay.controls.insert(2,pan_tool)
elif tool == 'move':
self.overlay.controls.pop(2)
self.overlay.controls.insert(2,move_tool)
elif tool == 'box_select':
self.overlay.controls.pop(2)
self.overlay.controls.insert(2,box_select_tool)
elif tool == 'brush':
self.overlay.controls.pop(2)
self.overlay.controls.insert(2,brush_tool)
elif tool == 'fill':
self.overlay.controls.pop(2)
self.overlay.controls.insert(2,fill_tool)
else:
pass
self.update()
class ImageStack(ft.Container):
def add_canvas_background(self):
image = self.page.canvas_background
canvas_bg = LayerImage(
left = 0,
top = 0,
width = self.width,
height = self.height,
content = ft.Image(
src_base64 = flet_utils.convert_image_to_base64(image),
width = 256,
height = 256,
repeat = 'repeat',
gapless_playback = True,
),
)
canvas_bg.image = image
canvas_bg.offset_x = 0
canvas_bg.offset_y = 0
self.canvas_bg = canvas_bg
self.content.controls.append(canvas_bg)
def add_layer_image(self, image):
layer_image = None
if image.path == None:
layer_image = LayerImage(
left = 0,
top = 0,
width = image.width,
height = image.height,
content = ft.Image(
src_base64 = flet_utils.convert_image_to_base64(image),
width = image.width,
height = image.height,
gapless_playback = True,
),
)
else:
layer_image = LayerImage(
left = 0,
top = 0,
width = image.width,
height = image.height,
content = ft.Image(
src = f'{image.path}',
width = image.width,
height = image.height,
gapless_playback = True,
),
)
layer_image.image = image
layer_image.offset_x = 0
layer_image.offset_y = 0
self.center_layer(layer_image)
self.content.controls.append(layer_image)
return layer_image
def get_preview(self):
stack = self.content.controls
return flet_utils.get_preview_from_stack(self.page.canvas_size, stack)
def refresh_stack(self):
self.content.controls.clear()
for slot in self.page.visible_layers:
self.content.controls.insert(0, slot.layer_image)
self.content.controls.insert(0, self.canvas_bg)
self.update()
def get_scaled_size(self):
self.scaled_width = self.width * self.scale
self.scaled_height = self.height * self.scale
def center_layer(self, layer_image):
layer_image.offset_x = 0
layer_image.offset_y = 0
layer_image.left = (self.width * 0.5) - (layer_image.width * 0.5)
layer_image.top = (self.height * 0.5) - (layer_image.height * 0.5)
def align_layer(self, layer_image):
layer_image.left = ((self.width - layer_image.width) * 0.5) + layer_image.offset_x
layer_image.top = ((self.height - layer_image.height) * 0.5) + layer_image.offset_y
def move_layer(self, e: ft.DragUpdateEvent):
layer = self.page.active_layer.layer_image
layer.offset_x += e.delta_x
layer.offset_y += e.delta_y
self.align_layer(layer)
self.update()
def finish_move_layer(self, e: ft.DragEndEvent):
canvas.refresh_canvas()
def resize_layer(self, e: ft.DragUpdateEvent):
pass
def box_select(self, e):
pass
def bucket_fill(self, e):
pass
class LayerImage(ft.Container):
pass
class CanvasGestures(ft.GestureDetector):
pass
class CanvasOverlay(ft.Stack):
def refresh_canvas_overlay(self):
self.refresh_canvas_size_display()
self.page.refresh_canvas_preview()
def refresh_canvas_size_display(self):
self.size_display.content.value = str(self.page.canvas_size)
self.update()
def clear_tools(self):
for tool in canvas_tools.content.controls:
tool.selected = False
# ImageStack == ft.Container
image_stack = ImageStack(
width = 4096,
height = 4096,
left = 0,
top = 0,
scale = 1.0,
content = ft.Stack(),
)
image_stack.offset_x = 0
image_stack.offset_y = 0
image_stack.scaled_width = image_stack.width
image_stack.scaled_height = image_stack.height
canvas_frame = ft.Container(
width = 4096,
height = 4096,
top = 0,
left = 0,
scale = 1.0,
image_fit = 'cover',
alignment = ft.alignment.center,
content = ft.Image(
src_base64 = flet_utils.get_canvas_frame((512,512)),
gapless_playback = True,
),
)
# CanvasGestures == ft.GestureDetector
def pan_canvas(e):
canvas.pan_canvas(e)
pan_tool = CanvasGestures(
mouse_cursor = ft.MouseCursor.GRAB,
drag_interval = 10,
on_pan_update = pan_canvas,
)
def select_layer(e):
pass
def move_layer(e):
image_stack.move_layer(e)
def finish_move_layer(e):
image_stack.finish_move_layer(e)
move_tool = CanvasGestures(
mouse_cursor = ft.MouseCursor.MOVE,
drag_interval = 10,
on_pan_start = select_layer,
on_pan_update = move_layer,
on_pan_end = finish_move_layer,
)
def set_select_start(e):
pass
def draw_select_box(e):
pass
def get_box_select(e):
pass
box_select_tool = CanvasGestures(
mouse_cursor = ft.MouseCursor.GRAB,
drag_interval = 10,
on_pan_start = set_select_start,
on_pan_update = draw_select_box,
on_pan_end = get_box_select,
)
def draw_on_layer(e):
pass
brush_tool = CanvasGestures(
mouse_cursor = ft.MouseCursor.GRAB,
drag_interval = 10,
on_pan_update = draw_on_layer,
)
def fill_selection(e):
pass
fill_tool = CanvasGestures(
mouse_cursor = ft.MouseCursor.GRAB,
drag_interval = 10,
on_tap = fill_selection,
)
canvas_size_display = ft.Container(
content = ft.Text(
value = "test",
),
left = 4,
bottom = 4,
padding = 4,
border_radius = 10,
opacity = 0.5,
bgcolor = 'black',
)
def center_canvas(e):
canvas.center_canvas()
center_canvas_button = ft.IconButton(
content = ft.Icon(ft.icons.FILTER_CENTER_FOCUS_OUTLINED),
tooltip = 'center canvas',
on_click = center_canvas,
)
def set_pan_tool(e):
e.page.set_current_tool(e)
pan_canvas_button = ft.IconButton(
content = ft.Icon(ft.icons.PAN_TOOL_OUTLINED),
tooltip = 'pan canvas',
on_click = set_pan_tool,
selected = True,
data = {'label':'pan'},
)
def zoom_in_canvas(e):
canvas.zoom_in(e)
zoom_in_button = ft.IconButton(
content = ft.Icon(ft.icons.ZOOM_IN_OUTLINED),
tooltip = 'zoom in canvas',
on_click = zoom_in_canvas,
)
def zoom_out_canvas(e):
canvas.zoom_out(e)
zoom_out_button = ft.IconButton(
content = ft.Icon(ft.icons.ZOOM_OUT_OUTLINED),
tooltip = 'zoom out canvas',
on_click = zoom_out_canvas,
)
canvas_tools = ft.Container(
content = ft.Column(
controls = [
center_canvas_button,
pan_canvas_button,
zoom_in_button,
zoom_out_button,
],
horizontal_alignment = 'end',
),
top = 4,
right = 4,
padding = 4,
border_radius = 10,
opacity = 0.5,
bgcolor = 'black',
disabled = False,
)
canvas_tools.center = center_canvas_button
canvas_tools.zoom_in = zoom_in_button
canvas_tools.zoom_out = zoom_out_button
# CanvasOverlay == ft.Stack
canvas_overlay = CanvasOverlay(
[
canvas_frame,
pan_tool,
canvas_size_display,
canvas_tools,
],
)
canvas_overlay.frame = canvas_frame
canvas_overlay.size_display = canvas_size_display
canvas_overlay.tools = canvas_tools
# Canvas = ft.Container
canvas = Canvas(
content = ft.Stack(
[
image_stack,
canvas_overlay,
],
),
clip_behavior = 'antiAlias',
alignment = ft.alignment.center,
expand = True,
)
canvas.image_stack = image_stack
canvas.overlay = canvas_overlay

View File

@ -0,0 +1,140 @@
# flet_file_manager.py
# Flet imports
import flet as ft
# other imports
from math import pi
from typing import Dict
from loguru import logger
# utils imports
from scripts import flet_utils
class UploadWindow(ft.AlertDialog):
def upload_file(self, e):
if file_picker.result is not None and file_picker.result.files is not None:
file_list = []
for f in file_picker.result.files:
upload_url = e.page.get_upload_url(f.name, 600)
img = ft.FilePickerUploadFile(f.name,upload_url)
file_list.append(img)
file_picker.upload(file_list)
def upload_complete(self, e):
self.progress_bars.clear()
self.selected_files.controls.clear()
e.page.close_uploads(e)
e.page.add_images_as_layers(file_picker.images)
file_picker.images.clear()
e.page.message('File upload(s) complete.')
def get_image_from_uploads(self, name):
return flet_utils.get_image_from_uploads(name)
def get_file_display(self, name, progress):
display = ft.Row(
controls = [
progress,
ft.Text(name),
],
)
return display
class ImportWindow(ft.AlertDialog):
pass
selected_files = ft.Column(
scroll = 'auto',
tight = True,
controls = [],
);
progress_bars: Dict[str, ft.ProgressBar] = {}
def upload_file(e):
uploads.upload_file(e)
def close_upload_window(e):
e.page.close_uploads(e)
uploads = UploadWindow(
title = ft.Text("Confirm file upload(s)"),
content = selected_files,
actions_alignment = "center",
actions = [
ft.ElevatedButton("UPLOAD", on_click = upload_file),
ft.TextButton("CANCEL", on_click = close_upload_window),
],
)
uploads.selected_files = selected_files
uploads.progress_bars = progress_bars
def import_file(e):
e.page.close_imports(e)
def close_import_window(e):
e.page.close_imports(e)
imports = ImportWindow(
title = ft.Text("Confirm file import(s)"),
content = selected_files,
actions_alignment = "center",
actions = [
ft.ElevatedButton("IMPORT", on_click = import_file),
ft.TextButton("CANCEL", on_click = close_import_window),
],
)
imports.selected_files = selected_files
imports.progress_bars = progress_bars
def pick_images(e: ft.FilePickerResultEvent):
progress_bars.clear()
selected_files.controls.clear()
file_picker.images.clear()
# check to see if files or directory were chosen
if e.files is not None and e.path is None:
for f in e.files:
prog = ft.ProgressRing(
width = 12,
height = 12,
stroke_width = 2,
value = 0,
color = 'blue',
)
progress_bars[f.name] = prog
selected_files.controls.append(uploads.get_file_display(f.name,prog))
file_picker.pending += 1
# upload if remote, import if local
if e.page.web:
e.page.open_uploads(e)
else:
e.page.open_imports(e)
def on_image_upload(e: ft.FilePickerUploadEvent):
if e.error:
e.page.message(f"Upload error occurred! Failed to fetch '{e.file_name}'.",1)
file_picker.pending -= 1
else:
# update progress bars
progress_bars[e.file_name].value = e.progress
progress_bars[e.file_name].update()
if e.progress >= 1:
file_picker.pending -= 1
file_picker.images.append(uploads.get_image_from_uploads(e.file_name))
if file_picker.pending <= 0:
file_picker.pending = 0
uploads.upload_complete(e)
file_picker = ft.FilePicker(
on_result = pick_images,
on_upload = on_image_upload
)
file_picker.pending = 0
file_picker.images = []

View File

@ -0,0 +1,152 @@
# flet_gallery_window.py
# Flet imports
import flet as ft
# utils imports
from scripts import flet_utils
class GalleryWindow(ft.AlertDialog):
def setup(self):
self.refresh_galleries()
def refresh_galleries(self):
self.refresh_gallery('uploads')
self.refresh_gallery('outputs')
def refresh_gallery(self, gallery_name):
index = None
if gallery_name == 'uploads':
self.uploads_gallery.get_gallery_display(gallery_name)
elif gallery_name == 'outputs':
self.outputs_gallery.get_gallery_display(gallery_name)
else:
page.message(f'{gallery_name} gallery not found.', 1)
return None
def get_gallery_images(self, gallery_name):
return flet_utils.get_gallery_images(gallery_name)
def select_image(self, e):
if e.control.border :
e.control.border = None
if e.control.image in self.selected_images:
self.selected_images.remove(e.control.image)
e.control.update()
else:
e.control.border = ft.border.all(2, e.page.tertiary_color)
self.selected_images.append(e.control.image)
e.control.update()
class GalleryDisplay(ft.Container):
def get_gallery_display(self, gallery_name):
self.content = ft.GridView(
controls = None,
padding = 0,
runs_count = 3,
run_spacing = 12,
spacing = 12,
expand = True,
)
gallery = gallery_window.get_gallery_images(gallery_name)
if not gallery:
self.content.controls.append(
ft.Image(
src = '/images/chickens.jpg',
tooltip = 'Nothing here but us chickens!',
gapless_playback = True,
)
)
return
for image in gallery:
gallery_image = GalleryImage(
content = ft.Image(
src = image.path,
tooltip = image.filename,
width = image.width,
height = image.height,
gapless_playback = True,
),
image_fit = 'contain',
height = image.height,
width = image.width,
padding = 0,
margin = 0,
border = None,
on_click = gallery_window.select_image
)
gallery_image.image = image
self.content.controls.append(gallery_image)
class GalleryImage(ft.Container):
pass
def add_as_new_layer(e):
if gallery_window.selected_images:
e.page.add_images_as_layers(gallery_window.selected_images)
gallery_window.selected_images.clear()
for tab in gallery_window.content.content.tabs:
for image in tab.content.content.controls:
image.border = None
image.update()
def save_to_disk(e):
pass
def remove_from_gallery(e):
pass
uploads_gallery = GalleryDisplay(
content = None,
clip_behavior = 'antiAlias',
)
outputs_gallery = GalleryDisplay(
content = None,
clip_behavior = 'antiAlias',
)
# GalleryWindow == ft.AlertDialog
gallery_window = GalleryWindow(
title = ft.Text('Gallery'),
content = ft.Container(
content = ft.Tabs(
selected_index = 0,
animation_duration = 300,
tabs = [
ft.Tab(
text = "Uploads",
content = uploads_gallery,
),
ft.Tab(
text = "Outputs",
content = outputs_gallery,
),
],
),
),
actions = [
ft.ElevatedButton(
text = "Add As New Layer(s)",
icon = ft.icons.ADD_OUTLINED,
on_click = add_as_new_layer,
),
ft.ElevatedButton(
text = "Save",
icon = ft.icons.SAVE_OUTLINED,
on_click = save_to_disk,
),
ft.ElevatedButton(
text = "Discard",
icon = ft.icons.DELETE_OUTLINED,
on_click = remove_from_gallery,
),
],
actions_alignment="end",
)
gallery_window.uploads_gallery = uploads_gallery
gallery_window.outputs_gallery = outputs_gallery
gallery_window.selected_images = []

View File

@ -0,0 +1,142 @@
# flet_messages.py
# Flet imports
import flet as ft
# utils imports
from scripts import flet_utils
class Messages(ft.Container):
def setup(self):
self.height = self.page.bottom_panel_height
self.bgcolor = self.page.primary_color
self.padding = self.page.container_padding
self.margin = self.page.container_margin
self.set_tab_text_size(self.page.text_size)
self.set_tab_bgcolor(self.page.secondary_color)
self.set_tab_padding(self.page.container_padding)
self.set_tab_margin(self.page.container_margin)
self.dragbar.content.height = self.page.divider_height
self.dragbar.content.color = self.page.tertiary_color
def on_page_change(self):
self.height = self.page.bottom_panel_height
self.bgcolor = self.page.primary_color
self.padding = self.page.container_padding
self.margin = self.page.container_margin
self.set_tab_text_size(self.page.text_size)
self.set_tab_bgcolor(self.page.secondary_color)
self.set_tab_padding(self.page.container_padding)
self.set_tab_margin(self.page.container_margin)
self.dragbar.content.height = self.page.divider_height
self.dragbar.content.color = self.page.tertiary_color
def set_tab_text_size(self, size):
for tab in self.tabs:
tab.tab_content.size = size
def set_tab_bgcolor(self, color):
for tab in self.tabs:
tab.content.bgcolor = color
def set_tab_padding(self, padding):
for tab in self.tabs:
tab.content.padding = padding
def set_tab_margin(self, margin):
for tab in self.tabs:
tab.content.margin = margin
def resize_messages(self, e: ft.DragUpdateEvent):
self.page.bottom_panel_height = max(100, self.page.bottom_panel_height - e.delta_y)
self.height = self.page.bottom_panel_height
self.page.update()
def message(self, text, err = 0):
if err:
text = "ERROR: " + text
self.add_message_to_messages(err,text)
flet_utils.log_message(text)
def prune_messages(self):
if len(message_list.controls) > self.page.max_message_history:
message_list.controls.pop(0)
message_list.update()
def add_message_to_messages(self,err,text):
if err:
msg = ft.Text(value = text, color = ft.colors.RED)
else:
msg = ft.Text(value = text)
message_list.controls.append(msg)
self.prune_messages()
message_list = ft.ListView(
spacing = 4,
auto_scroll = True,
controls = [],
)
messages_panel = ft.Container(
content = message_list,
)
video_editor_panel = ft.Column(
expand = True,
controls = [ft.Text("Under Construction")]
)
def resize_messages(e):
messages.resize_messages(e)
def realign_canvas(e):
e.page.align_canvas()
messages_dragbar = ft.GestureDetector(
mouse_cursor = ft.MouseCursor.RESIZE_ROW,
drag_interval = 50,
on_pan_update = resize_messages,
on_pan_end = realign_canvas,
content = ft.Divider(),
)
messages = Messages(
content = ft.Stack(
controls = [
messages_dragbar,
ft.Tabs(
selected_index = 0,
animation_duration = 300,
tabs = [
ft.Tab(
content = messages_panel,
tab_content = ft.Text(
value = 'Messages',
),
),
ft.Tab(
content = video_editor_panel,
tab_content = ft.Text(
value = 'Video Editor',
),
),
],
),
],
),
clip_behavior = 'antiAlias',
)
messages.dragbar = messages_dragbar
messages.tabs = messages.content.controls[1].tabs
messages.messages_panel = messages_panel
messages.video_editor_panel = video_editor_panel
messages.message_list = message_list

View File

@ -0,0 +1,365 @@
# flet_property_manager.py
# Flet imports
import flet as ft
# utils imports
from scripts import flet_utils
class PropertyManager(ft.Container):
def setup(self):
self.width = self.page.right_panel_width
self.bgcolor = self.page.primary_color
self.padding = self.page.container_padding
self.margin = self.page.container_margin
self.set_tab_text_size(self.page.text_size)
self.set_tab_bgcolor(self.page.secondary_color)
self.set_tab_padding(self.page.container_padding)
self.set_tab_margin(self.page.container_margin)
self.dragbar.content.width = self.page.vertical_divider_width
self.dragbar.content.color = self.page.tertiary_color
self.property_panel.preview.width = self.page.right_panel_width
self.property_panel.preview_dragbar.content.content.height = self.page.divider_height
self.property_panel.preview_dragbar.content.content.color = self.page.tertiary_color
self.property_panel.canvas_properties_divider.content.height = self.page.divider_height
self.property_panel.canvas_properties_divider.content.color = self.page.tertiary_color
self.property_panel.layer_properties_divider.content.height = self.page.divider_height
self.property_panel.layer_properties_divider.content.color = self.page.tertiary_color
self.page.refresh_canvas_preview()
self.refresh_canvas_properties()
def on_page_change(self):
self.width = self.page.right_panel_width
self.bgcolor = self.page.primary_color
self.padding = self.page.container_padding
self.margin = self.page.container_margin
self.set_tab_text_size(self.page.text_size)
self.set_tab_bgcolor(self.page.secondary_color)
self.set_tab_padding(self.page.container_padding)
self.set_tab_margin(self.page.container_margin)
self.dragbar.content.width = self.page.vertical_divider_width
self.dragbar.content.color = self.page.tertiary_color
self.property_panel.preview.width = self.page.right_panel_width
self.property_panel.preview_dragbar.content.content.height = self.page.divider_height
self.property_panel.preview_dragbar.content.content.color = self.page.tertiary_color
self.property_panel.canvas_properties_divider.content.height = self.page.divider_height
self.property_panel.canvas_properties_divider.content.color = self.page.tertiary_color
self.property_panel.layer_properties_divider.content.height = self.page.divider_height
self.property_panel.layer_properties_divider.content.color = self.page.tertiary_color
def set_tab_text_size(self, size):
for tab in self.tabs:
tab.tab_content.size = size
def set_tab_bgcolor(self, color):
for tab in self.tabs:
tab.content.content.bgcolor = color
def set_tab_padding(self, padding):
for tab in self.tabs:
tab.content.padding = padding
def set_tab_margin(self, margin):
for tab in self.tabs:
tab.content.margin = margin
def set_preview_size(self, width):
self.property_panel.preview.width = width
def set_preview_image(self, image):
self.property_panel.preview.content.src_base64 = flet_utils.convert_image_to_base64(image)
self.property_panel.update()
def refresh_canvas_properties(self):
self.property_panel.refresh_canvas_properties()
def refresh_layer_properties(self):
self.property_panel.refresh_layer_properties()
def resize_property_manager(self, e: ft.DragUpdateEvent):
self.page.right_panel_width = max(250, self.page.right_panel_width - e.delta_x)
self.width = self.page.right_panel_width
self.property_panel.preview.width = self.page.right_panel_width
self.page.update()
class PropertyPanel(ft.Container):
def resize_preview(self, e):
self.preview.height = max(200, self.preview.height + e.delta_y)
self.update()
def refresh_canvas_properties(self):
self.canvas_properties.controls[0].controls[1].value = self.page.canvas_size[0]
self.canvas_properties.controls[0].controls[3].value = self.page.canvas_size[1]
self.canvas_properties.update()
def refresh_layer_properties(self):
active = True if self.page.active_layer else False
if active:
self.layer_property_header.disabled = False
self.layer_property_header.open = True
self.layer_property_header.icon = ft.icons.ARROW_DROP_DOWN
self.layer_property_header.icon_color = self.page.tertiary_color
self.layer_properties.visible = True
self.layer_properties.controls[0].controls[0].value = self.page.active_layer.label.value
self.layer_properties.controls[1].controls[0].value = self.page.active_layer.image.width
self.layer_properties.controls[1].controls[1].value = self.page.active_layer.image.height
else:
self.layer_property_header.disabled = True
self.layer_property_header.open = False
self.layer_property_header.icon = ft.icons.ARROW_RIGHT
self.layer_property_header.icon_color = None
self.layer_properties.visible = False
self.update()
preview_pane = ft.Container(
content = ft.Image(
src_base64 = None,
gapless_playback = True,
),
image_fit = 'contain',
bgcolor = 'black',
height = 200,
padding = 0,
margin = 0,
)
def resize_preview(e):
property_panel.resize_preview(e)
preview_dragbar = ft.GestureDetector(
mouse_cursor = ft.MouseCursor.RESIZE_ROW,
drag_interval = 50,
on_pan_update = resize_preview,
content = ft.Container(
content = ft.Divider(),
margin = 0,
padding = 0,
),
)
def open_close_canvas_properties(e):
if canvas_property_header.open:
e.control.icon = ft.icons.ARROW_RIGHT
e.control.icon_color = None
canvas_property_header.open = False
canvas_properties.visible = False
property_panel.update()
else:
e.control.icon = ft.icons.ARROW_DROP_DOWN
e.control.icon_color = e.page.tertiary_color
canvas_property_header.open = True
canvas_properties.visible = True
property_panel.update()
canvas_property_header = ft.Column(
controls = [
ft.TextButton(
text = "Canvas Properties",
icon = ft.icons.ARROW_RIGHT,
on_click = open_close_canvas_properties,
),
],
)
canvas_property_header.open = False
canvas_properties = ft.Column(
visible = False,
controls = [
ft.Row(
controls = [
ft.Text(
value = 'Width:',
text_align = 'center',
no_wrap = True,
expand = 2,
),
ft.Text(
value = 0,
text_align = 'start',
expand = 1,
),
ft.Text(
value = 'Height:',
text_align = 'start',
no_wrap = True,
expand = 2,
),
ft.Text(
value = 0,
text_align = 'center',
expand = 1,
),
],
),
],
)
canvas_property_divider = ft.Container(
content = ft.Divider(),
margin = 0,
padding = 0,
)
def open_close_layer_properties(e):
if layer_property_header.open:
e.control.icon = ft.icons.ARROW_RIGHT
e.control.icon_color = None
layer_property_header.open = False
layer_properties.visible = False
property_panel.update()
else:
e.control.icon = ft.icons.ARROW_DROP_DOWN
e.control.icon_color = e.page.tertiary_color
layer_property_header.open = True
layer_properties.visible = True
property_panel.update()
layer_property_header = ft.TextButton(
text = "Layer Properties",
icon = ft.icons.ARROW_RIGHT,
on_click = open_close_layer_properties,
disabled = True,
)
layer_property_header.open = False
def update_layer_name(e):
e.page.active_layer.label.value = e.control.value
e.page.asset_manager.update()
layer_properties = ft.Column(
visible = False,
controls = [
ft.Row(
controls = [
ft.TextField(
label = 'Layer Name',
value = '',
text_align = 'center',
content_padding = 0,
expand = 1,
on_submit = update_layer_name,
),
],
),
ft.Row(
controls = [
ft.TextField(
label = 'Width',
value = 0,
text_align = 'center',
content_padding = 0,
expand = 1,
),
ft.TextField(
label = 'Height',
value = 0,
text_align = 'center',
content_padding = 0,
expand = 1,
),
],
),
],
)
layer_property_divider = ft.Container(
content = ft.Divider(),
margin = 0,
padding = 0,
)
property_panel = PropertyPanel(
content = ft.Column(
controls = [
preview_pane,
preview_dragbar,
canvas_property_header,
canvas_properties,
canvas_property_divider,
layer_property_header,
layer_properties,
layer_property_divider,
],
),
)
property_panel.preview = preview_pane
property_panel.preview_dragbar = preview_dragbar
property_panel.canvas_property_header = canvas_property_header
property_panel.canvas_properties_divider = canvas_property_divider
property_panel.canvas_properties = canvas_properties
property_panel.layer_property_header = layer_property_header
property_panel.layer_properties = layer_properties
property_panel.layer_properties_divider = layer_property_divider
output_panel = PropertyPanel(
content = ft.Column(
controls = [
ft.Text("Under Construction."),
],
),
)
def resize_property_manager(e):
property_manager.resize_property_manager(e)
def realign_canvas(e):
e.page.align_canvas()
property_manager_dragbar = ft.GestureDetector(
mouse_cursor = ft.MouseCursor.RESIZE_COLUMN,
drag_interval = 50,
on_pan_update = resize_property_manager,
on_pan_end = realign_canvas,
content = ft.VerticalDivider()
)
property_manager = PropertyManager(
content = ft.Row(
controls = [
property_manager_dragbar,
ft.Column(
controls = [
ft.Tabs(
selected_index = 0,
animation_duration = 300,
tabs = [
ft.Tab(
content = property_panel,
tab_content = ft.Text(
value = "Properties",
),
),
ft.Tab(
content = output_panel,
tab_content = ft.Text(
value = "Output",
),
),
],
),
],
alignment = 'start',
expand = True
),
ft.VerticalDivider(
width = 4,
opacity = 0,
),
],
expand = True,
),
clip_behavior = 'antiAlias',
)
property_manager.tabs = property_manager.content.controls[1].controls[0].tabs
property_manager.dragbar = property_manager_dragbar
property_manager.property_panel = property_panel
property_manager.output_panel = output_panel

View File

@ -0,0 +1,203 @@
# flet_settings_window.py
# Flet imports
import flet as ft
# utils imports
from scripts import flet_utils
class SettingsWindow(ft.AlertDialog):
def setup(self,settings):
self.get_settings_window_tabs(settings)
def get_settings_window_tab_page_setting_slider(self,settings,section,setting,display_width):
setting_slider = []
setting_value = None
if settings[setting]['value_type'] == 'int':
setting_value = int(settings[setting]['value'])
elif settings[setting]['value_type'] == 'float':
setting_value = float(settings[setting]['value'])
else:
setting_value = settings[setting]['value']
label = ft.Text(
value = setting,
text_align = 'center',
)
row = SettingsDisplay(
width = display_width,
data = [self, section, setting],
controls = [],
)
slider = ft.Slider(
value = setting_value,
label = "{value}",
min = settings[setting]['min'],
max = settings[setting]['max'],
divisions = int((settings[setting]['max'] - settings[setting]['min']) / settings[setting]['step']),
on_change = row.settings_window_tab_slider_changed,
data = row,
expand = 4,
)
value = ft.TextField(
value = setting_value,
on_submit = row.settings_window_tab_slider_changed,
data = row,
content_padding = 10,
expand = 1,
)
row.controls.extend([slider,value])
setting_slider.extend([label,row])
return setting_slider
def get_settings_window_tab_settings(self, settings, section):
settings = settings[section]
section_settings = [ft.Divider(height=10, color='gray')]
display_width = (self.content.width * 0.5) - 5
for setting in settings:
if 'value' not in settings[setting]:
continue
new_row = SettingsDisplay()
new_row
display = None
display_type = settings[setting]['display']
if display_type == 'dropdown':
option_list = []
for i in range(len(settings[setting]['option_list'])):
item = ft.dropdown.Option(
text = settings[setting]['option_list'][i]
)
option_list.append(item)
display = ft.Dropdown(
label = setting,
value = settings[setting]['value'],
options = option_list,
on_change = new_row.settings_window_tab_setting_changed,
data = section,
content_padding = 10,
width = display_width,
)
elif display_type == 'textinput':
display = ft.TextField(
label = setting,
value = settings[setting]['value'],
on_submit = new_row.settings_window_tab_setting_changed,
data = section,
content_padding = 10,
width = display_width,
)
elif display_type == 'bool':
display = ft.Switch(
label = setting,
value = settings[setting]['value'],
on_change = new_row.settings_window_tab_setting_changed,
data = section,
width = display_width,
)
elif display_type == 'slider':
display = ft.Column(
controls = self.get_settings_window_tab_page_setting_slider(settings,section,setting,display_width),
)
else:
continue
new_row.data = [self, section, setting]
new_row.controls.append(display)
section_settings.append(new_row)
return section_settings
def get_settings_window_tab_page(self, settings, section):
settings_window_tab_page = ft.Column(
alignment = 'start',
scroll = 'auto',
controls = self.get_settings_window_tab_settings(settings, section),
)
return settings_window_tab_page
def get_settings_window_tabs(self, settings):
tabs = []
for section in settings:
if section.endswith('_page'):
tab = ft.Tab(
text = section.split('_')[0],
content = self.get_settings_window_tab_page(settings, section),
)
tabs.append(tab)
self.content.content.tabs = tabs
def update_settings_window_tab(self, section):
settings = self.page.session.get('settings')
for i, tab in enumerate(self.content.content.tabs):
if section.startswith(tab.text):
self.content.content.tabs[i].content = self.get_settings_window_tab_page(settings, section)
return
def update_settings_window(self):
self.get_settings_window_tabs(self.page.session.get('settings'))
self.page.update()
class SettingsDisplay(ft.Row):
def settings_window_tab_setting_changed(self, e):
settings = self.page.session.get('settings')
settings[e.control.data][e.control.label]['value'] = e.control.value
update_settings_window_tab(e.control.data)
self.page.update()
def settings_window_tab_slider_changed(self, e):
settings = self.page.session.get('settings')
parent = e.control.data
setting = settings[parent.data[1]][parent.data[2]]
setting_value = None
if setting['value_type'] == 'int':
setting_value = int(e.control.value)
elif setting['value_type'] == 'float':
setting_value = float(e.control.value)
else:
setting_value = e.control.value
setting['value'] = setting_value
parent.controls[0].value = setting_value
parent.controls[1].value = str(setting_value)
parent.data[0].update_settings_window_tab(parent.data[1])
self.page.update()
def apply_settings(e):
settings_window.update_settings_window()
def save_settings(e):
save_settings_to_config()
settings_window.update_settings_window()
def reset_settings(e):
reset_settings_from_config()
settings_window.update_settings_window()
# SettingsWindow == ft.AlertDialog
settings_window = SettingsWindow(
title = ft.Text("Settings"),
content = ft.Container(
content = ft.Tabs(
selected_index = 0,
animation_duration = 300,
tabs = None,
),
),
actions = [
ft.ElevatedButton(
text = "Apply",
icon = ft.icons.CHECK_CIRCLE,
on_click = apply_settings,
),
ft.ElevatedButton(
text = "Save",
icon = ft.icons.SAVE,
on_click = save_settings,
),
ft.ElevatedButton(
text = "Restore Defaults",
icon = ft.icons.RESTORE_FROM_TRASH_ROUNDED,
on_click = reset_settings,
),
],
actions_alignment = "end",
)

View File

@ -0,0 +1,134 @@
# flet_appbar.py
# Flet imports
import flet as ft
# utils imports
from scripts import flet_utils
class TitleBar(ft.Container):
def setup(self):
self.width = self.page.width
self.height = self.page.titlebar_height
self.title.size = self.page.titlebar_height * 0.5
self.title.color = self.page.tertiary_color
self.prompt.text_size = max(12, self.page.titlebar_height * 0.25)
self.prompt.focused_border_color = self.page.tertiary_color
self.layout_menu.controls[0].text_size = self.page.text_size
self.theme_switcher.size = self.page.titlebar_height
self.theme_switcher.icon_size = self.page.titlebar_height * 0.5
self.theme_switcher.tooltip = f"Click to change between the light and dark themes. Current {'(Light theme)' if self.page.theme_mode == 'light' else '(Dark theme)'}"
self.theme_switcher.on_click = self.page.change_theme_mode
self.settings_button.size = self.page.titlebar_height
self.settings_button.icon_size = self.page.titlebar_height * 0.5
self.settings_button.on_click = self.page.open_settings
def on_page_change(self):
self.width = self.page.width
self.height = self.page.titlebar_height
self.title.size = self.page.titlebar_height * 0.5
self.title.color = self.page.tertiary_color
self.prompt.text_size = max(12, self.page.titlebar_height * 0.25)
self.prompt.focused_border_color = self.page.tertiary_color
self.layout_menu.controls[0].text_size = self.page.text_size
self.theme_switcher.size = self.page.titlebar_height
self.theme_switcher.icon_size = self.page.titlebar_height * 0.5
self.theme_switcher.tooltip = f"Click to change between the light and dark themes. Current {'(Light theme)' if self.page.theme_mode == 'light' else '(Dark theme)'}"
self.settings_button.size = self.page.titlebar_height
self.settings_button.icon_size = self.page.titlebar_height * 0.5
title = ft.Text(
value = " Sygil ",
text_align = 'center',
)
prompt = ft.TextField(
value = "",
min_lines = 1,
max_lines = 1,
content_padding = ft.padding.only(left = 12, top = 0, right = 0, bottom = 0),
shift_enter = True,
autofocus = True,
expand = True,
tooltip = "Prompt to use for generation.",
hint_text = "A corgi wearing a top hat as an oil painting.",
)
generate_button = ft.ElevatedButton(
text = "Generate",
on_click = None,
)
def set_layout(e):
e.page.set_layout(e)
layout_menu = ft.Row(
alignment = 'start',
controls = [
ft.Dropdown(
options = [
ft.dropdown.Option(text="Default"),
ft.dropdown.Option(text="Textual Inversion"),
ft.dropdown.Option(text="Node Editor"),
],
value = 'Default',
text_size = 20,
# alignment = ft.alignment.center,
content_padding = ft.padding.only(left = 12, top = 0, right = 0, bottom = 0),
tooltip = "Switch between different workspaces",
on_change = set_layout,
)
],
)
layout_menu.text_size = layout_menu.controls[0].text_size
theme_switcher = ft.IconButton(
ft.icons.WB_SUNNY_OUTLINED,
)
settings_button = ft.IconButton(
icon = ft.icons.SETTINGS,
)
option_list = ft.Row(
controls = [
ft.Container(content = layout_menu),
ft.Container(content = theme_switcher),
ft.Container(content = settings_button),
],
alignment = 'end'
)
# TitleBar == ft.Container
titlebar = TitleBar(
content = ft.Row(
controls = [
title,
prompt,
#generate_button,
option_list,
],
),
)
titlebar.title = title
titlebar.prompt = prompt
titlebar.generate_button = generate_button
titlebar.layout_menu = layout_menu
titlebar.theme_switcher = theme_switcher
titlebar.settings_button = settings_button

View File

@ -0,0 +1,245 @@
# flet_tool_manager.py
# Flet imports
import flet as ft
# utils imports
from scripts import flet_utils
def open_gallery(e):
e.page.open_gallery(e)
def blank_layer(e):
e.page.add_blank_layer()
def load_images(e):
e.page.load_images()
def tool_select(e):
e.page.set_current_tool(e)
class Action():
def __init__(self, label, icon, tooltip, on_click):
self.label = label
self.icon = icon
self.tooltip = tooltip
self.on_click = on_click
self.disabled = False
action_list = [
Action('gallery', ft.icons.DASHBOARD_OUTLINED, 'Gallery', open_gallery),
Action('blank layer', ft.icons.ADD_OUTLINED, 'Add blank layer', blank_layer),
Action('load image', ft.icons.IMAGE_OUTLINED, 'Load image as layer', load_images),
]
class Tool():
def __init__(self, label, icon, tooltip):
self.label = label
self.icon = icon
self.tooltip = tooltip
self.on_click = tool_select
self.disabled = True
tool_list = [
Tool('move', ft.icons.OPEN_WITH_OUTLINED, 'Move layer(s)'),
Tool('select', ft.icons.HIGHLIGHT_ALT_OUTLINED, 'Select tool'),
Tool('brush', ft.icons.BRUSH_OUTLINED, 'Brush tool'),
Tool('fill', ft.icons.FORMAT_COLOR_FILL_OUTLINED, 'Fill tool'),
]
class ToolManager(ft.Container):
def setup(self):
self.toolbox.get_tools()
self.width = self.page.tool_manager_width
self.bgcolor = self.page.primary_color
self.padding = self.page.container_padding
self.margin = self.page.container_margin
self.toolbox.bgcolor = self.page.secondary_color
self.toolbox.padding = self.page.container_padding
self.toolbox.margin = self.page.container_margin
self.tool_divider.height = self.page.divider_height
self.tool_divider.color = self.page.tertiary_color
self.tool_properties.bgcolor = self.page.secondary_color
self.tool_properties.padding = self.page.container_padding
self.tool_properties.margin = self.page.container_margin
self.dragbar.width = self.page.vertical_divider_width
self.dragbar.color = self.page.tertiary_color
def on_page_change(self):
self.width = self.page.tool_manager_width
self.bgcolor = self.page.primary_color
self.padding = self.page.container_padding
self.margin = self.page.container_margin
self.toolbox.bgcolor = self.page.secondary_color
self.toolbox.padding = self.page.container_padding
self.toolbox.margin = self.page.container_margin
self.tool_divider.height = self.page.divider_height
self.tool_divider.color = self.page.tertiary_color
self.tool_properties.bgcolor = self.page.secondary_color
self.tool_properties.padding = self.page.container_padding
self.tool_properties.margin = self.page.container_margin
self.dragbar.width = self.page.vertical_divider_width
self.dragbar.color = self.page.tertiary_color
def resize_tool_manager(self, e: ft.DragUpdateEvent):
self.page.tool_manager_width = max(50, self.page.tool_manager_width + e.delta_x)
tool_manager.width = self.page.tool_manager_width
self.page.update()
def resize_toolbox(self, e: ft.DragUpdateEvent):
min_height = (self.page.tool_manager_button_size * 2)
self.page.toolbox_height = max(min_height, self.page.toolbox_height + e.delta_y)
toolbox.height = self.page.toolbox_height
self.update()
def enable_tools(self):
for tool in self.toolbox.content.controls:
try:
if tool.on_click == tool_select:
tool.disabled = False
except AttributeError:
continue # is divider
self.update()
def disable_tools(self):
for tool in self.toolbox.content.controls:
try:
if tool.on_click == tool_select:
tool.disabled = True
except AttributeError:
continue # is divider
self.update()
def clear_tools(self):
self.toolbox.clear_tools()
class ToolBox(ft.Container):
def get_tools(self):
for action in action_list:
self.content.controls.append(self.make_button(action))
divider = ft.Container(
content = ft.Divider(
height = self.page.divider_height,
color = self.page.tertiary_color,
),
margin = 0,
padding = ft.padding.only(left = 10, top = 0, right = 0, bottom = 0),
)
self.content.controls.append(divider)
for tool in tool_list:
self.content.controls.append(self.make_button(tool))
tool_manager.update()
def make_button(self,button_info):
button = ft.IconButton(
width = self.page.icon_size * 2,
icon_size = self.page.icon_size,
content = ft.Icon(button_info.icon),
selected = False,
selected_icon_color = self.page.tertiary_color,
tooltip = button_info.tooltip,
data = {'label':button_info.label},
on_click = button_info.on_click,
disabled = button_info.disabled,
)
return button
def clear_tools(self):
for control in self.content.controls:
control.selected = False
class ToolPropertyPanel(ft.Container):
pass
# ToolBox == ft.Container
toolbox = ToolBox(
clip_behavior = 'antiAlias',
content = ft.Row(
alignment = 'start',
wrap = True,
spacing = 0,
run_spacing = 0,
controls = [],
),
margin = 0,
padding = ft.padding.only(left = 15, top = 0, right = 0, bottom = 0),
)
def resize_toolbox(e):
tool_manager.resize_toolbox(e)
tool_divider = ft.GestureDetector(
mouse_cursor = ft.MouseCursor.RESIZE_ROW,
drag_interval = 50,
on_pan_update = resize_toolbox,
content = ft.Container(
content = ft.Divider(),
margin = 0,
padding = ft.padding.only(left = 10, top = 0, right = 0, bottom = 0),
),
)
# ToolPropertyPanel == ft.Container
tool_properties = ToolPropertyPanel(
content = ft.Column(
controls = [],
)
)
def resize_tool_manager(e):
tool_manager.resize_tool_manager(e)
def realign_canvas(e):
e.page.align_canvas()
tool_manager_dragbar = ft.GestureDetector(
mouse_cursor = ft.MouseCursor.RESIZE_COLUMN,
drag_interval = 50,
on_pan_update = resize_tool_manager,
on_pan_end = realign_canvas,
content = ft.VerticalDivider(),
)
# ToolManager = ft.Container
tool_manager = ToolManager(
content = ft.Row(
controls = [
ft.Column(
controls = [
toolbox,
tool_divider,
tool_properties,
],
alignment = 'start',
expand = True,
),
tool_manager_dragbar,
],
expand = True,
),
clip_behavior = 'antiAlias',
)
tool_manager.toolbox = toolbox
tool_manager.tool_divider = tool_divider.content.content
tool_manager.tool_properties = tool_properties
tool_manager.dragbar = tool_manager_dragbar.content

View File

@ -0,0 +1,175 @@
# flet_utils.py
# imports
import os, yaml, base64
from PIL import Image, ImageDraw
from io import BytesIO
from datetime import datetime
from pprint import pprint
# logging
def log_message(message):
log_file = None
# get time and format message
prefix = datetime.now()
msg_prefix = prefix.strftime("%Y/%m/%d %H:%M:%S")
message = msg_prefix + " " + message
# check to see if we're appending to current logfile or making a new one'
try:
log_file = log_message.log
except AttributeError:
log_prefix = prefix.strftime("%Y%m%d_%H%M%S")
os.makedirs('log/webui/flet', exist_ok=True)
log_message.log = os.path.join('log/webui/flet',log_prefix+'webui_flet.log')
log_file = log_message.log
# write message to logfile
with open(log_file,'a+') as log:
log.write(message)
# settings
path_to_default_config = 'configs/webui/webui_flet.yaml'
path_to_user_config = 'configs/webui/userconfig_flet.yaml'
def get_default_settings_from_config():
with open(path_to_default_config) as f:
default_settings = yaml.safe_load(f)
return default_settings
def get_user_settings_from_config():
# get default settings
settings = get_default_settings_from_config()
# check to see if userconfig exists
if os.path.exists(path_to_user_config):
# compare to see which is newer
default_time = os.path.getmtime(path_to_default_config)
user_time = os.path.getmtime(path_to_user_config)
# if default is newer, save over userconfig and exit early
if (default_time > user_time):
save_user_settings_to_config(settings)
return settings
# else, load userconfig
with open(path_to_user_config) as f:
user_settings = yaml.safe_load(f)
settings.update(user_settings)
# regardless, return settings as dict
return settings
def save_user_settings_to_config(settings):
with open(path_to_user_config, 'w+') as f:
yaml.dump(settings, f, default_flow_style=False)
# image handling
path_to_assets = "webui/flet/assets"
path_to_uploads = "webui/flet/assets/uploads"
path_to_outputs = "webui/flet/assets/outputs"
# creates blank image (to do: take size as arg)
def create_blank_image(size):
try:
create_blank_image.count +=1
except AttributeError:
create_blank_image.count = 1
name = 'blank_layer_' + str(create_blank_image.count).zfill(2)
img = Image.new('RGBA',size,(0,0,0,0))
img.filename = name
img.path = None
return img
# takes name of image
# returns dict
# name of image : image handle
def get_image_from_uploads(name):
path_to_image = os.path.join(path_to_uploads, name)
if os.path.exists(path_to_image):
image = Image.open(path_to_image)
image = image.convert("RGBA")
image.filename = name
image.path = path_to_image
return image
else:
log_message(f'image not found: "{name}"')
return None
def get_canvas_background(path):
image = Image.open(path)
image = image.convert("RGBA")
return image
# make canvas frame
def get_canvas_frame(canvas_size):
image = Image.new('RGBA',(4096,4096),(0,0,0,127))
canvas_width = canvas_size[0]
canvas_height = canvas_size[1]
x0 = int((image.width - canvas_width) * 0.5)
y0 = int((image.height - canvas_height) * 0.5)
x1 = x0 + canvas_width
y1 = y0 + canvas_height
box = (x0, y0, x1, y1)
image.paste((0,0,0,0), box)
return convert_image_to_base64(image)
# takes list of Image(s) as arg
# returns single composite of all images
def get_preview_from_stack(size, stack):
preview = Image.new('RGBA',size,(0,0,0,0))
canvas_width = size[0]
canvas_height = size[1]
for layer in stack:
image = layer.image
# need to crop images for composite
x0 = ((image.width - canvas_width) * 0.5) - layer.offset_x
y0 = ((image.height - canvas_height) * 0.5) - layer.offset_y
x1 = x0 + canvas_width
y1 = y0 + canvas_height
box = (x0, y0, x1, y1)
cropped_image = image.crop(box)
preview = Image.alpha_composite(preview,cropped_image)
return preview
# converts Image to base64 string
def convert_image_to_base64(image):
image_buffer = BytesIO()
image.save(image_buffer, format='PNG')
image_buffer.seek(0)
image_bytes = image_buffer.getvalue()
return base64.b64encode(image_bytes).decode()
# takes name of gallery as arg ('assets','output','uploads')
# returns list of dicts
# name of image:
# 'img_path' : path_to_image
# 'info_path' : path_to_yaml
def get_gallery_images(gallery_name):
path_to_gallery = None
images = []
files = []
if gallery_name == 'uploads':
path_to_gallery = path_to_uploads
elif gallery_name == 'outputs':
path_to_gallery = path_to_outputs
else:
log_message(f'gallery not found: "{gallery_name}"')
return images
if os.path.exists(path_to_gallery):
files = os.listdir(path_to_gallery)
else:
return None
for f in files:
if f.endswith(('.jpg','.jpeg','.png')):
image = Image.open(os.path.join(path_to_gallery,f))
image = image.convert("RGBA")
image.filename = f
image.path = os.path.join(gallery_name,f)
images.append(image)
return images
# textual inversion
textual_inversion_grid_row_list = [
'model', 'medium', 'artist', 'trending', 'movement', 'flavors', 'techniques', 'tags',
]
def run_textual_inversion(args):
pass

447
webui/flet/webui_flet.py Normal file
View File

@ -0,0 +1,447 @@
# Flet imports
import flet as ft
# other imports
import os
from math import pi
from typing import Optional
from loguru import logger
import logging
#logging.basicConfig(level=logging.DEBUG)
# utils imports
from scripts import flet_utils
from scripts.flet_settings_window import settings_window
from scripts.flet_gallery_window import gallery_window
from scripts.flet_file_manager import file_picker, uploads, imports
from scripts.flet_titlebar import titlebar
from scripts.flet_tool_manager import tool_manager
from scripts.flet_asset_manager import asset_manager, layer_action_menu
from scripts.flet_canvas import canvas
from scripts.flet_messages import messages
from scripts.flet_property_manager import property_manager
# for debugging
from pprint import pprint
os.environ["FLET_WS_MAX_MESSAGE_SIZE"] = "8000000"
# main ###############################################################
@logger.catch(reraise=True)
def main(page: ft.Page):
# init ###############################################################
# messages
page.messages = messages
page.message = messages.message
page.max_message_history = 50
# ui
page.current_layout = 'Default'
page.titlebar_height = 50
page.bottom_panel_height = page.height * 0.2
page.toolbox_height = 250
page.tool_manager_width = 50
page.tool_manager_button_size = 40
page.left_panel_width = 200
page.right_panel_width = 250
page.background_color = None
page.primary_color = None
page.secondary_color = 'white_10'
page.tertiary_color = 'blue'
page.text_color = None
page.text_size = 14
page.icon_size = 20
page.padding = 0
page.margin = 0
page.container_padding = 0
page.container_margin = 0
page.tab_color = 'white_10'
page.tab_padding = ft.padding.only(left = 2, top = 12, right = 2, bottom = 8)
page.tab_margin = 0
page.divider_height = 10
page.vertical_divider_width = 10
# titlebar
page.titlebar = titlebar
def change_theme_mode(e):
page.theme_mode = "dark" if page.theme_mode == "light" else "light"
if "(Light theme)" in titlebar.theme_switcher.tooltip:
titlebar.theme_switcher.tooltip = titlebar.theme_switcher.tooltip.replace("(Light theme)", '')
if "(Dark theme)" in titlebar.theme_switcher.tooltip:
titlebar.theme_switcher.tooltip = titlebar.theme_switcher.tooltip.replace("(Dark theme)", '')
titlebar.theme_switcher.tooltip += "(Light theme)" if page.theme_mode == "light" else "(Dark theme)"
page.update()
page.change_theme_mode = change_theme_mode
# tools
page.tool_manager = tool_manager
page.current_tool = 'pan'
def enable_tools():
page.tool_manager.enable_tools()
page.enable_tools = enable_tools
def disable_tools():
page.tool_manager.disable_tools()
page.disable_tools = disable_tools
def set_current_tool(e):
page.tool_manager.clear_tools()
page.canvas.clear_tools()
e.control.selected = True
page.current_tool = e.control.data['label']
page.canvas.set_current_tool(e.control.data['label'])
page.update()
page.set_current_tool = set_current_tool
# asset manager
page.asset_manager = asset_manager
page.active_layer = None
page.visible_layers = []
page.layer_height = 50
def set_active_layer(layer_slot):
if page.active_layer == layer_slot:
return
page.active_layer = layer_slot
page.enable_tools()
page.property_manager.refresh_layer_properties()
page.set_active_layer = set_active_layer
def add_blank_layer():
image = flet_utils.create_blank_image(page.canvas_size)
layer_slot = page.asset_manager.add_image_as_layer(image)
layer_slot.layer_image = page.canvas.add_layer_image(image)
page.message("added blank layer to canvas")
page.refresh_layers()
page.add_blank_layer = add_blank_layer
def add_images_as_layers(images):
layer_slots = page.asset_manager.add_images_as_layers(images)
for slot in layer_slots:
slot.layer_image = page.canvas.add_layer_image(slot.image)
page.message(f'added "{slot.image.filename}" as layer')
page.refresh_layers()
page.add_images_as_layers = add_images_as_layers
def load_images():
page.file_picker.pick_files(file_type = 'image', allow_multiple = True)
page.load_images = load_images
# canvas
page.canvas = canvas
page.canvas_background = flet_utils.get_canvas_background('webui/flet/assets/images/default_grid_texture.png')
page.canvas_size = [512,512]
def get_viewport_size():
viewport_width = page.width - (page.tool_manager_width + (page.vertical_divider_width * 3) + page.left_panel_width + page.right_panel_width)
viewport_height = page.height - (page.titlebar_height * 2) - page.bottom_panel_height
return viewport_width, viewport_height
page.get_viewport_size = get_viewport_size
def align_canvas():
page.canvas.align_canvas()
page.align_canvas = align_canvas
# property manager
page.property_manager = property_manager
def refresh_canvas_preview():
preview = page.canvas.get_image_stack_preview()
page.property_manager.set_preview_image(preview)
page.refresh_canvas_preview = refresh_canvas_preview
def refresh_layers():
if page.active_layer == None:
page.disable_tools()
else:
page.enable_tools()
page.asset_manager.refresh_layers()
page.canvas.refresh_canvas()
page.refresh_canvas_preview()
page.property_manager.refresh_layer_properties()
page.update()
page.refresh_layers = refresh_layers
# layouts
def set_layout(e):
page.current_layout = e.control.value
page.update()
page.set_layout = set_layout
def on_page_change(e):
page.titlebar.on_page_change()
page.tool_manager.on_page_change()
page.asset_manager.on_page_change()
page.canvas.on_page_change()
page.messages.on_page_change()
page.property_manager.on_page_change()
full_page.width = page.width
full_page.height = page.height
page.update()
page.on_resize = on_page_change
def on_window_change(e):
if e.data == 'minimize' or e.data == 'close':
return
else:
page.on_page_change(e)
page.on_window_event = on_window_change
# settings
def load_settings():
settings = flet_utils.get_user_settings_from_config()
page.session.set('settings',settings)
page.update()
def save_settings_to_config():
settings = page.session.get('settings')
flet_utils.save_user_settings_to_config(settings)
def reset_settings_from_config():
settings = flet_utils.get_default_settings_from_config()
page.session.remove('settings')
page.session.set('settings',settings)
save_settings_to_config()
if not page.session.contains_key('settings'):
load_settings()
settings = page.session.get('settings')
try:
ui_settings = settings['webui_page']
page.theme_mode = ui_settings['default_theme']['value']
MAX_MESSAGE_HISTORY = ui_settings['max_message_history']['value']
except AttributeError:
page.message("Config load error: missing setting.",1)
page.session.set('layout','default')
# settings window ####################################################
def close_settings_window(e):
settings_window.open = False
page.update()
page.close_settings = close_settings_window
def open_settings_window(e):
page.dialog = settings_window
settings_window.open = True
page.update()
page.open_settings = open_settings_window
page.settings_window = settings_window
settings_window.content.width = page.width * 0.50
settings_window.content.bgcolor = page.primary_color
settings_window.content.padding = page.container_padding
settings_window.content.margin = page.container_margin
# gallery window #####################################################
def close_gallery_window(e):
gallery_window.open = False
page.update()
page.close_gallery = close_gallery_window
def open_gallery_window(e):
page.dialog = gallery_window
gallery_window.open = True
page.update()
page.open_gallery = open_gallery_window
page.gallery_window = gallery_window
page.refresh_gallery = gallery_window.refresh_gallery
gallery_window.content.width = page.width * 0.5
gallery_window.content.bgcolor = page.primary_color
gallery_window.content.padding = page.container_padding
gallery_window.content.margin = page.container_margin
gallery_window.outputs_gallery.height = page.height * 0.75
gallery_window.outputs_gallery.bgcolor = page.primary_color
gallery_window.outputs_gallery.padding = page.container_padding
gallery_window.outputs_gallery.margin = page.container_margin
gallery_window.uploads_gallery.height = page.height * 0.75
gallery_window.uploads_gallery.bgcolor = page.primary_color
gallery_window.uploads_gallery.padding = page.container_padding
gallery_window.uploads_gallery.margin = page.container_margin
# file manager #######################################################
def close_upload_window(e):
uploads.open = False
page.update()
page.close_uploads = close_upload_window
def open_upload_window(e):
page.dialog = uploads
uploads.open = True
page.update()
page.open_uploads = open_upload_window
def close_import_window(e):
imports.open = False
page.update()
page.close_imports = close_import_window
def open_import_window(e):
page.dialog = imports
imports.open = True
page.update()
page.open_imports = open_import_window
page.uploads = uploads
page.imports = imports
page.file_picker = file_picker
page.overlay.append(file_picker)
# center panel #############################################################
text_editor = ft.Container(
content = ft.Text('Under Construction.'),
bgcolor = page.secondary_color,
expand = True,
)
viewport = ft.Container(
bgcolor = page.primary_color,
padding = page.container_padding,
margin = page.container_margin,
content = ft.Tabs(
selected_index = 0,
animation_duration = 300,
tabs = [
ft.Tab(
content = canvas,
tab_content = ft.Text(
value = 'Canvas',
size = page.text_size,
),
),
ft.Tab(
text = 'Text Editor',
content = text_editor,
tab_content = ft.Text(
value = 'Text Editor',
size = page.text_size,
),
),
],
),
expand = True,
)
center_panel = ft.Container(
content = ft.Column(
controls = [
viewport,
messages,
],
),
bgcolor = page.primary_color,
padding = page.container_padding,
margin = page.container_margin,
expand = True,
)
# layouts ############################################################
default_layout = ft.Row(
controls = [
tool_manager,
asset_manager,
center_panel,
property_manager,
],
expand=True,
)
current_layout = default_layout
# workspace ##########################################################
workspace = ft.Column(
controls = [
titlebar,
current_layout,
],
expand = True,
)
page.workspace = workspace
full_page = ft.Stack(
expand = True,
controls = [
workspace,
layer_action_menu,
],
height = page.height,
width = page.width,
)
page.full_page = full_page
page.title = "Stable Diffusion Playground"
page.add(full_page)
page.settings_window.setup(page.session.get('settings'))
page.gallery_window.setup()
page.titlebar.setup()
page.tool_manager.setup()
page.asset_manager.setup()
page.canvas.setup()
page.messages.setup()
page.property_manager.setup()
page.update()
ft.app(target=main, route_url_strategy="path", port=8505, assets_dir="assets", upload_dir="assets/uploads")