From 394824ce88df4c0f36d5f99eea313a3d30650aaa Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sat, 7 Jun 2025 23:44:47 +0500 Subject: [PATCH] Add layout system and file selection logic Introduce a new layout system for managing main window content and tabs. Integrate file selection and drag-and-drop functionality for adding files to the conversion list, with automatic tab switching to "Added Files". Refactor existing components to support these features. --- .../application/convertor/items_to_convert.go | 24 ++- internal/application/convertor/queue.go | 11 +- internal/controller/convertor.go | 8 +- internal/controller/main.go | 9 +- internal/gui/view/convertor.go | 149 ++++++++++++++- internal/gui/window/layout.go | 173 ++++++++++++++++++ internal/gui/window/main.go | 152 +++------------ 7 files changed, 381 insertions(+), 145 deletions(-) create mode 100644 internal/gui/window/layout.go diff --git a/internal/application/convertor/items_to_convert.go b/internal/application/convertor/items_to_convert.go index 420b42a..32062c9 100644 --- a/internal/application/convertor/items_to_convert.go +++ b/internal/application/convertor/items_to_convert.go @@ -10,7 +10,7 @@ import ( ) type ItemsToConvertContract interface { - Add(file *File) + Add(file *ffmpeg.File) Clear() GetItems() map[int]ItemToConvertContract GetItemsContainer() *fyne.Container @@ -41,7 +41,7 @@ func (items *itemsToConvert) GetItemsContainer() *fyne.Container { return items.itemsContainer } -func (items *itemsToConvert) Add(file *File) { +func (items *itemsToConvert) Add(file *ffmpeg.File) { nextId := items.nextId var content *fyne.Container var buttonPlay *widget.Button @@ -49,9 +49,15 @@ func (items *itemsToConvert) Add(file *File) { buttonPlay = widget.NewButtonWithIcon("", theme.Icon(theme.IconNameMediaPlay), func() { buttonPlay.Disable() go func() { - //_ = items.ffplayService.Run(FFplaySetting{ - // PathToFile: file.Path, - //}) + ffplay, err := items.ffmpeg.GetFFplay() + if err != nil { + fyne.Do(func() { + buttonPlay.Enable() + }) + return + } + + _ = ffplay.Play(file) fyne.Do(func() { buttonPlay.Enable() }) @@ -108,23 +114,23 @@ func (items *itemsToConvert) Clear() { } type ItemToConvertContract interface { - GetFile() *File + GetFile() *ffmpeg.File GetContent() *fyne.Container } type itemToConvert struct { - file *File + file *ffmpeg.File content *fyne.Container } -func newItemToConvert(file *File, content *fyne.Container) ItemToConvertContract { +func newItemToConvert(file *ffmpeg.File, content *fyne.Container) ItemToConvertContract { return &itemToConvert{ file: file, content: content, } } -func (item *itemToConvert) GetFile() *File { +func (item *itemToConvert) GetFile() *ffmpeg.File { return item.file } diff --git a/internal/application/convertor/queue.go b/internal/application/convertor/queue.go index 919957a..d15beb6 100644 --- a/internal/application/convertor/queue.go +++ b/internal/application/convertor/queue.go @@ -1,9 +1,12 @@ package convertor -import "errors" +import ( + "errors" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg" +) type Queue struct { - Setting *ConvertSetting + Setting *ffmpeg.ConvertSetting Status StatusContract Error error } @@ -46,7 +49,7 @@ type QueueListenerContract interface { type QueueListContract interface { AddListener(queueListener QueueListenerContract) GetItems() map[int]*Queue - Add(setting *ConvertSetting) + Add(setting *ffmpeg.ConvertSetting) EventChangeQueue(key int, queue *Queue) Remove(key int) GetItem(key int) (*Queue, error) @@ -72,7 +75,7 @@ func (l *queueList) GetItems() map[int]*Queue { return l.items } -func (l *queueList) Add(setting *ConvertSetting) { +func (l *queueList) Add(setting *ffmpeg.ConvertSetting) { queue := Queue{ Setting: setting, Status: StatusType(Waiting), diff --git a/internal/controller/convertor.go b/internal/controller/convertor.go index eba2f76..4037000 100644 --- a/internal/controller/convertor.go +++ b/internal/controller/convertor.go @@ -1,15 +1,21 @@ package controller import ( + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/download/service" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/view" ) func (c *controller) convertor() { - content := view.Convertor() + content := view.Convertor(c.window, c.addFileForConversion) c.window.SetContent(content) } +func (c *controller) addFileForConversion(file ffmpeg.File) { + c.app.GetItemsToConvert().Add(&file) + c.window.GetLayout().GetRContainer().SelectAddedFilesTab() +} + func (c *controller) settingConvertor(isAllowCancellation bool) { ffmpegPath := c.app.GetFFmpegService().GetFFmpegPath() ffprobePath := c.app.GetFFmpegService().GetFFprobePath() diff --git a/internal/controller/main.go b/internal/controller/main.go index c1c2efd..db86488 100644 --- a/internal/controller/main.go +++ b/internal/controller/main.go @@ -22,8 +22,13 @@ func NewController(app application.AppContract) ControllerContract { app.GetQueueService().AddListener(queueLayout) return &controller{ - app: app, - window: window.NewMainWindow(fyneWindow, app.GetProgressBarService(), app.GetItemsToConvert(), queueLayout), + app: app, + window: window.NewMainWindow( + fyneWindow, + app.GetProgressBarService(), + app.GetItemsToConvert(), + queueLayout, + ), } } diff --git a/internal/gui/view/convertor.go b/internal/gui/view/convertor.go index db11d16..41a3cb8 100644 --- a/internal/gui/view/convertor.go +++ b/internal/gui/view/convertor.go @@ -1,14 +1,24 @@ package view import ( + "errors" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/lang" + "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/widget" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/window" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel" + "image/color" + "os" + "path/filepath" ) -func Convertor() fyne.CanvasObject { - form := newFormConvertor() +func Convertor(window window.WindowContract, addFileForConversion func(file ffmpeg.File)) fyne.CanvasObject { + form := newFormConvertor(window, addFileForConversion) converterVideoFilesTitle := lang.L("converterVideoFilesTitle") return widget.NewCard(converterVideoFilesTitle, "", container.NewVScroll(form.getForm())) @@ -17,17 +27,146 @@ func Convertor() fyne.CanvasObject { type formConvertor struct { form *widget.Form items []*widget.FormItem + + window window.WindowContract + addFileForConversion func(file ffmpeg.File) } -func newFormConvertor() *formConvertor { +func newFormConvertor(window window.WindowContract, addFileForConversion func(file ffmpeg.File)) *formConvertor { f := widget.NewForm() f.SubmitText = lang.L("converterVideoFilesSubmitTitle") - return &formConvertor{ - form: f, + formConvertor := &formConvertor{ + form: f, + window: window, + addFileForConversion: addFileForConversion, } + + fileForConversion := formConvertor.newFileForConversion() + + items := []*widget.FormItem{ + { + Text: lang.L("fileForConversionTitle"), + Widget: fileForConversion.button, + }, + { + Widget: container.NewHScroll(fileForConversion.message), + }, + } + formConvertor.form.Items = items + formConvertor.items = items + + return formConvertor } func (f *formConvertor) getForm() *widget.Form { return f.form } + +type fileForConversion struct { + button *widget.Button + message *canvas.Text + file *kernel.File + + changeCallbacks map[int]func(err error) +} + +func (f *formConvertor) newFileForConversion() *fileForConversion { + message := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255}) + fileForConversion := &fileForConversion{ + message: message, + + changeCallbacks: map[int]func(err error){}, + } + + buttonTitle := lang.L("choose") + "\n" + + lang.L("or") + "\n" + + lang.L("dragAndDropFiles") + + var locationURI fyne.ListableURI + + fileForConversion.button = widget.NewButton(buttonTitle, func() { + f.window.NewFileOpen(func(r fyne.URIReadCloser, err error) { + fyne.Do(func() { + fileForConversion.message.Text = "" + fileForConversion.message.Refresh() + }) + + if err != nil { + fyne.Do(func() { + fileForConversion.message.Text = err.Error() + fileForConversion.message.Refresh() + }) + fileForConversion.eventSelectFile(err) + return + } + if r == nil { + return + } + + f.addFileForConversion(ffmpeg.File{ + Path: r.URI().Path(), + Name: r.URI().Name(), + Ext: r.URI().Extension(), + }) + + fileForConversion.eventSelectFile(nil) + + listableURI := storage.NewFileURI(filepath.Dir(r.URI().Path())) + locationURI, _ = storage.ListerForURI(listableURI) + }, locationURI) + }) + + f.window.SetOnDropped(func(position fyne.Position, uris []fyne.URI) { + if len(uris) == 0 { + return + } + + isError := false + for _, uri := range uris { + info, err := os.Stat(uri.Path()) + if err != nil { + isError = true + continue + } + if info.IsDir() { + isError = true + continue + } + + f.addFileForConversion(ffmpeg.File{ + Path: uri.Path(), + Name: uri.Name(), + Ext: uri.Extension(), + }) + + fileForConversion.eventSelectFile(nil) + + listableURI := storage.NewFileURI(filepath.Dir(uri.Path())) + locationURI, _ = storage.ListerForURI(listableURI) + } + + if isError { + fileForConversion.message.Text = lang.L("errorDragAndDropFile") + utils.SetStringErrorStyle(fileForConversion.message) + fileForConversion.eventSelectFile(errors.New(fileForConversion.message.Text)) + } else { + fyne.Do(func() { + fileForConversion.message.Text = "" + fileForConversion.message.Refresh() + }) + } + }) + + return fileForConversion +} + +func (c *fileForConversion) addChangeCallback(callback func(err error)) { + c.changeCallbacks[len(c.changeCallbacks)] = callback +} + +func (c *fileForConversion) eventSelectFile(err error) { + for _, changeCallback := range c.changeCallbacks { + changeCallback(err) + } +} diff --git a/internal/gui/window/layout.go b/internal/gui/window/layout.go new file mode 100644 index 0000000..b5423cd --- /dev/null +++ b/internal/gui/window/layout.go @@ -0,0 +1,173 @@ +package window + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/lang" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/convertor" +) + +type LayoutContract interface { + SetContent(content fyne.CanvasObject) fyne.CanvasObject + GetRContainer() RightMainContainerContract +} + +type layout struct { + layoutContainer *fyne.Container + rContainer RightMainContainerContract +} + +func NewLayout(progressBarService application.ProgressBarContract, itemsToConvert convertor.ItemsToConvertContract, queueLayout QueueLayoutContract) LayoutContract { + rContainer := newRightContainer(progressBarService.GetContainer(), itemsToConvert, queueLayout) + layoutContainer := container.NewAdaptiveGrid(2, widget.NewLabel(""), rContainer.GetCanvasObject()) + + return &layout{ + layoutContainer: layoutContainer, + rContainer: rContainer, + } +} + +func (l *layout) SetContent(content fyne.CanvasObject) fyne.CanvasObject { + l.layoutContainer.Objects[0] = content + return l.layoutContainer +} + +func (l *layout) GetRContainer() RightMainContainerContract { + return l.rContainer +} + +type RightMainContainerContract interface { + GetCanvasObject() fyne.CanvasObject + GetTabs() *container.AppTabs + SelectFileQueueTab() + SelectAddedFilesTab() +} + +type rightMainContainer struct { + container fyne.CanvasObject + tabs *container.AppTabs + addedFilesTab *container.TabItem + fileQueueTab *container.TabItem +} + +func newRightContainer(blockProgressbar *fyne.Container, itemsToConvert convertor.ItemsToConvertContract, queueLayout QueueLayoutContract) RightMainContainerContract { + addedFilesTab := container.NewTabItem(lang.L("addedFilesTitle"), addedFilesContainer(itemsToConvert)) + fileQueueTab := container.NewTabItem(lang.L("fileQueueTitle"), fileQueueContainer(queueLayout)) + + tabs := container.NewAppTabs( + addedFilesTab, + fileQueueTab, + ) + + rightContainer := container.NewBorder( + container.NewVBox( + blockProgressbar, + widget.NewSeparator(), + ), + nil, + nil, + nil, + tabs, + ) + + return &rightMainContainer{ + container: rightContainer, + tabs: tabs, + addedFilesTab: addedFilesTab, + fileQueueTab: fileQueueTab, + } +} + +func (r *rightMainContainer) GetCanvasObject() fyne.CanvasObject { + return r.container +} + +func (r *rightMainContainer) GetTabs() *container.AppTabs { + return r.tabs +} + +func (r *rightMainContainer) SelectFileQueueTab() { + fyne.Do(func() { + r.tabs.Select(r.fileQueueTab) + }) +} + +func (r *rightMainContainer) SelectAddedFilesTab() { + fyne.Do(func() { + r.tabs.Select(r.addedFilesTab) + }) +} + +func addedFilesContainer(itemsToConvert convertor.ItemsToConvertContract) *fyne.Container { + line := canvas.NewLine(theme.Color(theme.ColorNameFocus)) + line.StrokeWidth = 5 + checkboxAutoRemove := widget.NewCheck( + lang.L("autoClearAfterAddingToQueue"), + func(checked bool) { + itemsToConvert.SetIsAutoRemove(checked) + }, + ) + checkboxAutoRemove.SetChecked(itemsToConvert.GetIsAutoRemove()) + + buttonClear := widget.NewButton( + lang.L("clearAll"), + func() { + itemsToConvert.Clear() + }, + ) + buttonClear.Importance = widget.DangerImportance + return container.NewBorder( + container.NewVBox( + container.NewPadded(), + container.NewBorder(nil, nil, nil, buttonClear, container.NewHScroll(checkboxAutoRemove)), + container.NewPadded(), + line, + ), nil, nil, nil, + container.NewVScroll( + container.NewBorder( + nil, nil, nil, container.NewPadded(), + container.NewVBox( + container.NewPadded(), + itemsToConvert.GetItemsContainer(), + ), + ), + ), + ) +} + +func fileQueueContainer(queueLayout QueueLayoutContract) *fyne.Container { + title := widget.NewLabel(lang.L("queue")) + title.TextStyle.Bold = true + + line := canvas.NewLine(theme.Color(theme.ColorNameFocus)) + line.StrokeWidth = 5 + + queueLayout.GetQueueStatistics().GetWaiting().SetTitle(lang.L("waitingQueue")) + queueLayout.GetQueueStatistics().GetInProgress().SetTitle(lang.L("inProgressQueue")) + queueLayout.GetQueueStatistics().GetCompleted().SetTitle(lang.L("completedQueue")) + queueLayout.GetQueueStatistics().GetError().SetTitle(lang.L("errorQueue")) + queueLayout.GetQueueStatistics().GetTotal().SetTitle(lang.L("total")) + + return container.NewBorder( + container.NewVBox( + container.NewPadded(), + container.NewHBox(title, queueLayout.GetQueueStatistics().GetCompleted().GetCheckbox(), queueLayout.GetQueueStatistics().GetError().GetCheckbox()), + container.NewHBox(queueLayout.GetQueueStatistics().GetInProgress().GetCheckbox(), queueLayout.GetQueueStatistics().GetWaiting().GetCheckbox(), queueLayout.GetQueueStatistics().GetTotal().GetCheckbox()), + container.NewPadded(), + line, + ), nil, nil, nil, + container.NewVScroll( + container.NewBorder( + nil, nil, nil, container.NewPadded(), + container.NewVBox( + container.NewPadded(), + queueLayout.GetItemsContainer(), + ), + ), + ), + ) +} diff --git a/internal/gui/window/main.go b/internal/gui/window/main.go index 81d3ea0..990c9d0 100644 --- a/internal/gui/window/main.go +++ b/internal/gui/window/main.go @@ -2,12 +2,7 @@ package window import ( "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" - "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" - "fyne.io/fyne/v2/lang" - "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/convertor" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils" @@ -18,17 +13,25 @@ type WindowContract interface { Show() InitLayout() NewFileOpen(callback func(fyne.URIReadCloser, error), location fyne.ListableURI) *dialog.FileDialog + NewFolderOpen(callback func(fyne.ListableURI, error), location fyne.ListableURI) *dialog.FileDialog + SetOnDropped(callback func(position fyne.Position, uris []fyne.URI)) + GetLayout() LayoutContract } type mainWindow struct { fyneWindow fyne.Window - layout *fyne.Container + layout LayoutContract itemsToConvert convertor.ItemsToConvertContract progressBarService application.ProgressBarContract queueLayout QueueLayoutContract } -func NewMainWindow(fyneWindow fyne.Window, progressBarService application.ProgressBarContract, itemsToConvert convertor.ItemsToConvertContract, queueLayout QueueLayoutContract) WindowContract { +func NewMainWindow( + fyneWindow fyne.Window, + progressBarService application.ProgressBarContract, + itemsToConvert convertor.ItemsToConvertContract, + queueLayout QueueLayoutContract, +) WindowContract { fyneWindow.Resize(fyne.Size{Width: 1039, Height: 599}) fyneWindow.CenterOnScreen() @@ -42,14 +45,14 @@ func NewMainWindow(fyneWindow fyne.Window, progressBarService application.Progre func (w *mainWindow) InitLayout() { fyne.Do(func() { - rContainer := newRightContainer(w.progressBarService.GetContainer(), w.itemsToConvert, w.queueLayout) - layout := container.NewAdaptiveGrid(2, widget.NewLabel(""), rContainer.GetCanvasObject()) - w.fyneWindow.SetContent(layout) - - w.layout = layout + w.layout = NewLayout(w.progressBarService, w.itemsToConvert, w.queueLayout) }) } +func (w *mainWindow) GetLayout() LayoutContract { + return w.layout +} + func (w *mainWindow) NewFileOpen(callback func(fyne.URIReadCloser, error), location fyne.ListableURI) *dialog.FileDialog { fileDialog := dialog.NewFileOpen(callback, w.fyneWindow) utils.FileDialogResize(fileDialog, w.fyneWindow) @@ -60,6 +63,16 @@ func (w *mainWindow) NewFileOpen(callback func(fyne.URIReadCloser, error), locat return fileDialog } +func (w *mainWindow) NewFolderOpen(callback func(fyne.ListableURI, error), location fyne.ListableURI) *dialog.FileDialog { + fileDialog := dialog.NewFolderOpen(callback, w.fyneWindow) + utils.FileDialogResize(fileDialog, w.fyneWindow) + fileDialog.Show() + if location != nil { + fileDialog.SetLocation(location) + } + return fileDialog +} + func (w *mainWindow) SetContent(content fyne.CanvasObject) { fyne.Do(func() { if w.layout == nil { @@ -67,8 +80,7 @@ func (w *mainWindow) SetContent(content fyne.CanvasObject) { return } - w.layout.Objects[0] = content - w.fyneWindow.SetContent(w.layout) + w.fyneWindow.SetContent(w.layout.SetContent(content)) }) } @@ -76,116 +88,8 @@ func (w *mainWindow) Show() { w.fyneWindow.Show() } -type RightMainContainerContract interface { - GetCanvasObject() fyne.CanvasObject - GetTabs() *container.AppTabs - SelectFileQueueTab() - SelectAddedFilesTab() -} - -type rightMainContainer struct { - container fyne.CanvasObject - tabs *container.AppTabs - addedFilesTab *container.TabItem - fileQueueTab *container.TabItem -} - -func newRightContainer(blockProgressbar *fyne.Container, itemsToConvert convertor.ItemsToConvertContract, queueLayout QueueLayoutContract) RightMainContainerContract { - addedFilesTab := container.NewTabItem(lang.L("addedFilesTitle"), addedFilesContainer(itemsToConvert)) - fileQueueTab := container.NewTabItem(lang.L("fileQueueTitle"), fileQueueContainer(queueLayout)) - - tabs := container.NewAppTabs( - addedFilesTab, - fileQueueTab, - ) - - rightContainer := container.NewBorder( - container.NewVBox( - blockProgressbar, - widget.NewSeparator(), - ), - nil, - nil, - nil, - tabs, - ) - - return &rightMainContainer{ - container: rightContainer, - tabs: tabs, - addedFilesTab: addedFilesTab, - fileQueueTab: fileQueueTab, - } -} - -func (r *rightMainContainer) GetCanvasObject() fyne.CanvasObject { - return r.container -} - -func (r *rightMainContainer) GetTabs() *container.AppTabs { - return r.tabs -} - -func (r *rightMainContainer) SelectFileQueueTab() { +func (w *mainWindow) SetOnDropped(callback func(position fyne.Position, uris []fyne.URI)) { fyne.Do(func() { - r.tabs.Select(r.fileQueueTab) + w.fyneWindow.SetOnDropped(callback) }) } - -func (r *rightMainContainer) SelectAddedFilesTab() { - fyne.Do(func() { - r.tabs.Select(r.addedFilesTab) - }) -} - -func addedFilesContainer(itemsToConvert convertor.ItemsToConvertContract) *fyne.Container { - line := canvas.NewLine(theme.Color(theme.ColorNameFocus)) - line.StrokeWidth = 5 - checkboxAutoRemove := widget.NewCheck( - lang.L("autoClearAfterAddingToQueue"), - func(checked bool) { - itemsToConvert.SetIsAutoRemove(checked) - }, - ) - checkboxAutoRemove.SetChecked(itemsToConvert.GetIsAutoRemove()) - - buttonClear := widget.NewButton( - lang.L("clearAll"), - func() { - itemsToConvert.Clear() - }, - ) - buttonClear.Importance = widget.DangerImportance - return container.NewVBox( - container.NewPadded(), - container.NewBorder(nil, nil, nil, buttonClear, container.NewHScroll(checkboxAutoRemove)), - container.NewPadded(), - line, - container.NewPadded(), - itemsToConvert.GetItemsContainer(), - ) -} - -func fileQueueContainer(queueLayout QueueLayoutContract) *fyne.Container { - title := widget.NewLabel(lang.L("queue")) - title.TextStyle.Bold = true - - line := canvas.NewLine(theme.Color(theme.ColorNameFocus)) - line.StrokeWidth = 5 - - queueLayout.GetQueueStatistics().GetWaiting().SetTitle(lang.L("waitingQueue")) - queueLayout.GetQueueStatistics().GetInProgress().SetTitle(lang.L("inProgressQueue")) - queueLayout.GetQueueStatistics().GetCompleted().SetTitle(lang.L("completedQueue")) - queueLayout.GetQueueStatistics().GetError().SetTitle(lang.L("errorQueue")) - queueLayout.GetQueueStatistics().GetTotal().SetTitle(lang.L("total")) - - return container.NewVBox( - container.NewPadded(), - container.NewHBox(title, queueLayout.GetQueueStatistics().GetCompleted().GetCheckbox(), queueLayout.GetQueueStatistics().GetError().GetCheckbox()), - container.NewHBox(queueLayout.GetQueueStatistics().GetInProgress().GetCheckbox(), queueLayout.GetQueueStatistics().GetWaiting().GetCheckbox(), queueLayout.GetQueueStatistics().GetTotal().GetCheckbox()), - container.NewPadded(), - line, - container.NewPadded(), - queueLayout.GetItemsContainer(), - ) -}