diff --git a/aspect_ratio_helper/_constants.py b/aspect_ratio_helper/_constants.py index e497af6..e4b6a40 100644 --- a/aspect_ratio_helper/_constants.py +++ b/aspect_ratio_helper/_constants.py @@ -6,6 +6,7 @@ MIN_DIMENSION = 64 ARH_EXPAND_BY_DEFAULT_KEY = 'arh_expand_by_default' ARH_HIDE_ACCORDION_BY_DEFAULT_KEY = 'arh_hide_accordion_by_default' ARH_UI_COMPONENT_ORDER_KEY = 'arh_ui_component_order_key' +ARH_UI_JAVASCRIPT_SELECTION_METHOD = 'arh_ui_javascript_selection_method' ARH_JAVASCRIPT_ASPECT_RATIO_SHOW_KEY = 'arh_javascript_aspect_ratio_show' ARH_JAVASCRIPT_ASPECT_RATIOS_KEY = 'arh_javascript_aspect_ratio' ARH_SHOW_MAX_WIDTH_OR_HEIGHT_KEY = 'arh_show_max_width_or_height' diff --git a/aspect_ratio_helper/_settings.py b/aspect_ratio_helper/_settings.py index ad453ef..b1fa5b7 100644 --- a/aspect_ratio_helper/_settings.py +++ b/aspect_ratio_helper/_settings.py @@ -28,10 +28,10 @@ OPT_KEY_TO_DEFAULT_MAP = { _constants.ARH_HIDE_ACCORDION_BY_DEFAULT_KEY: True, _constants.ARH_UI_COMPONENT_ORDER_KEY: DEFAULT_UI_COMPONENT_ORDER_KEY, + _constants.ARH_UI_JAVASCRIPT_SELECTION_METHOD: 'Aspect Ratios Dropdown', _constants.ARH_JAVASCRIPT_ASPECT_RATIO_SHOW_KEY: True, _constants.ARH_JAVASCRIPT_ASPECT_RATIOS_KEY: - '1:1, 3:2, 4:3, 5:4, 16:9, 1.85:1, 2.35:1, 2.39:1, 2.40:1, ' - '21:9, 1.375:1, 1.66:1, 1.75:1', + '1:1, 3:2, 4:3, 5:4, 16:9', _constants.ARH_SHOW_MAX_WIDTH_OR_HEIGHT_KEY: False, _constants.ARH_MAX_WIDTH_OR_HEIGHT_KEY: _constants.MAX_DIMENSION / 2, @@ -110,6 +110,23 @@ def on_ui_settings(): section=_constants.SECTION, ), ) + shared.opts.add_option( + key=_constants.ARH_UI_JAVASCRIPT_SELECTION_METHOD, + info=shared.OptionInfo( + default=OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_UI_JAVASCRIPT_SELECTION_METHOD, + ), + label='JavaScript selection method', + component=gr.Dropdown, + component_args=lambda: { + 'choices': [ + 'Aspect Ratios Dropdown', + 'Default Options Button', + ], + }, + section=_constants.SECTION, + ), + ) # accordion options shared.opts.add_option( diff --git a/javascript/aspectRatioController.js b/javascript/aspectRatioController.js index 75edf5d..8a78c6c 100644 --- a/javascript/aspectRatioController.js +++ b/javascript/aspectRatioController.js @@ -26,13 +26,11 @@ const getCurrentImage = () => { return document.getElementById(currentTabImageId).querySelector('img'); } -// utility functions const roundToClosestMultiple = (num, multiple) => { - const rounded = Math.round(Number(num) / multiple) * multiple; + const rounded = Math.round(Number(num) / multiple) * multiple; return rounded; } - const aspectRatioFromStr = (ar) => { if (!ar.includes(':')) return; return ar.split(':').map(x => Number(x)); @@ -80,34 +78,35 @@ const reverseAllOptions = () => { }); } -class SelectController { +class OptionPickingController { constructor(page, defaultOptions, controller) { this.page = page; - this.options = [...new Set([...defaultOptions, ...getOptions()])]; + this.options = this.getOptions(defaultOptions); this.switchButton = gradioApp().getElementById(page + '_res_switch_btn'); const wrapperDiv = document.createElement('div'); wrapperDiv.setAttribute("id", `${this.page}_size_toolbox`); wrapperDiv.setAttribute("class", "flex flex-col relative col gap-4"); wrapperDiv.setAttribute("style", "min-width: min(320px, 100%); flex-grow: 0"); - wrapperDiv.innerHTML = ` -
- -
- `; + wrapperDiv.innerHTML = this.getElementInnerHTML(); const parent = this.switchButton.parentNode; parent.removeChild(this.switchButton); wrapperDiv.appendChild(this.switchButton); parent.insertBefore(wrapperDiv, parent.lastChild.previousElementSibling); + this.getPickerElement().onchange = this.pickerChanged(controller); + this.switchButton.onclick = this.switchButtonOnclick(controller); + } + + getOptions(defaultOptions) { + return [...new Set([...defaultOptions, ...getOptions()])]; + } + + pickerChanged(controller) { const originalBGC = this.switchButton.style.backgroundColor; - this.getSelectElement().onchange = () => { - const picked = this.getCurrentOption(); + return () => { + const picked = this.getCurrentOption(); if (_IMAGE === picked) { this.switchButton.disabled = true; this.switchButton.style.backgroundColor = 'black'; @@ -118,8 +117,10 @@ class SelectController { controller.setAspectRatio(picked); }; + } - this.switchButton.onclick = () => { + switchButtonOnclick(controller) { + return () => { reverseAllOptions(); const picked = this.getCurrentOption(); if (_LOCK === picked) { @@ -130,17 +131,88 @@ class SelectController { }; } - getSelectElement() { + getElementInnerHTML() { + throw new Error('Not implemented'); + } + + getPickerElement() { + throw new Error('Not implemented'); + } + + getCurrentOption() { + throw new Error('Not implemented'); + } +} + + +class SelectOptionPickingController extends OptionPickingController { + constructor(page, defaultOptions, controller) { + super(page, defaultOptions, controller); + } + + getElementInnerHTML() { + return ` +
+ +
+ `; + } + + getPickerElement() { return gradioApp().getElementById(`${this.page}_select_aspect_ratio`); } getCurrentOption() { - const selectElement = this.getSelectElement(); + const selectElement = this.getPickerElement(); const options = Array.from(selectElement); return options[selectElement.selectedIndex].value; } } +class DefaultOptionsButtonOptionPickingController extends OptionPickingController { + constructor(page, defaultOptions, controller) { + super(page, defaultOptions, controller); + this.currentIndex = 0; + this.getPickerElement().onclick = this.pickerChanged(controller); + } + + pickerChanged(controller) { + return () => { + this.currentIndex = (this.currentIndex + 1) % this.options.length; + this.getPickerElement().querySelector('button').textContent = this.getCurrentOption(); + super.pickerChanged(controller)(); + } + } + + getElementInnerHTML() { + const classes = Array.from(this.switchButton.classList); + return ` +
+ +
+ `; + } + + getPickerElement() { + return gradioApp().getElementById(`${this.page}_ar_default_options_button`); + } + + getOptions(defaultOptions) { + return defaultOptions; + } + + getCurrentOption() { + return this.options[this.currentIndex || 0]; + } +} + + class SliderController { constructor(element) { this.element = element @@ -201,7 +273,12 @@ class AspectRatioController { }); }) - this.selectController = new SelectController(page, defaultOptions, this); + if (window.opts.arh_ui_javascript_selection_method === 'Default Options Button') { + this.optionPickingControler = new DefaultOptionsButtonOptionPickingController(page, defaultOptions, this); + } else { + this.optionPickingControler = new SelectOptionPickingController(page, defaultOptions, this); + } + this.setAspectRatio(_OFF); } @@ -278,11 +355,17 @@ class AspectRatioController { const [width, height] = clampToBoundaries(w, h) - const inputEvent = new Event("input", { bubbles: true}); + const inputEvent = new Event("input", {bubbles: true}); this.widthContainer.setVal(width); this.widthContainer.triggerEvent(inputEvent); this.heightContainer.setVal(height); this.heightContainer.triggerEvent(inputEvent); + this.heightContainer.inputs.forEach(input => { + dimensionChange({target: input}, false, true); + }); + this.widthContainer.inputs.forEach(input => { + dimensionChange({target: input}, true, false); + }); } static observeStartup(key, page, defaultOptions, postSetup = (_) => {}) { @@ -291,7 +374,7 @@ class AspectRatioController { const heightContainer = gradioApp().querySelector(`#${page}_height`); // wait for width and height containers to exist. - if (widthContainer && heightContainer) { + if (widthContainer && heightContainer) { observer.disconnect(); if (!window.opts.arh_javascript_aspect_ratio_show) { return; @@ -319,7 +402,7 @@ const addImg2ImgTabSwitchClickListeners = (controller) => { img2imgTabButtons.forEach(button => { button.addEventListener('click', (_) => { // set aspect ratio is RECALLED to change to the image specific to the newly selected tab. - if (controller.selectController.getCurrentOption() === _IMAGE) { + if (controller.optionPickingControler.getCurrentOption() === _IMAGE) { controller.setAspectRatio(_IMAGE); } @@ -332,7 +415,7 @@ const addImg2ImgTabSwitchClickListeners = (controller) => { const postImageControllerSetupFunction = (controller) => { const scaleToImg2ImgImage = (e) => { - const picked = controller.selectController.getCurrentOption(); + const picked = controller.optionPickingControler.getCurrentOption(); if (picked !== _IMAGE) return; const files = e.dataTransfer ? e.dataTransfer.files : e.target.files; const img = new Image(); @@ -361,7 +444,7 @@ document.addEventListener("DOMContentLoaded", () => { AspectRatioController.observeStartup( "__img2imgAspectRatioController", "img2img", - [_OFF, _IMAGE, _LOCK], + [_OFF, _LOCK, _IMAGE], postImageControllerSetupFunction ); }); diff --git a/style.css b/style.css index 1dab878..88c5a86 100644 --- a/style.css +++ b/style.css @@ -26,6 +26,7 @@ margin-bottom: 10px; padding-left: 2px; text-align: center; + min-width: 2.5em; } .arh-btn-row button {