using System; using System.Collections.Generic; using System.Drawing; using CMLeonOS.UILib.Animations; namespace CMLeonOS.Gui.UILib { internal class Dropdown : Control { internal Dropdown(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height) { hostWindow = parent; OnClick = (_, _) => ToggleExpanded(); OnUnfocused = HandleUnfocused; } private readonly Window hostWindow; private Window popupWindow; private Table popupTable; private bool closeAnimationRunning = false; internal Action SelectionChanged; internal List Items { get; } = new List(); private string _placeholderText = "Select"; internal string PlaceholderText { get { return _placeholderText; } set { _placeholderText = value ?? string.Empty; Render(); } } private Color _background = UITheme.Surface; internal Color Background { get { return _background; } set { _background = value; Render(); } } private Color _foreground = UITheme.TextPrimary; internal Color Foreground { get { return _foreground; } set { _foreground = value; Render(); } } private Color _border = UITheme.SurfaceBorder; internal Color Border { get { return _border; } set { _border = value; Render(); } } private Color _highlight = UITheme.Accent; internal Color Highlight { get { return _highlight; } set { _highlight = value; Render(); } } private int _selectedIndex = -1; internal int SelectedIndex { get { return _selectedIndex; } set { int newIndex = value; if (newIndex < -1) { newIndex = -1; } if (newIndex >= Items.Count) { newIndex = Items.Count - 1; } if (_selectedIndex != newIndex) { _selectedIndex = newIndex; Render(); SyncPopupSelection(); } } } internal string SelectedText { get { if (_selectedIndex < 0 || _selectedIndex >= Items.Count) { return string.Empty; } return Items[_selectedIndex]; } } internal bool Expanded { get; private set; } = false; internal int MaxVisibleItems { get; set; } = 6; internal void RefreshItems() { if (_selectedIndex >= Items.Count) { _selectedIndex = Items.Count - 1; } RebuildPopup(); Render(); } private void HandleUnfocused() { Window nextFocus = WM.PendingFocusTarget; if (nextFocus == popupTable || nextFocus == popupWindow) { return; } Collapse(); } private void ToggleExpanded() { if (Expanded) { Collapse(); } else { Expand(); } } private void Expand() { if (Expanded) { return; } Expanded = true; RebuildPopup(); Render(); } internal void Collapse() { if (!Expanded || closeAnimationRunning) { return; } if (popupWindow == null) { Expanded = false; Render(); return; } closeAnimationRunning = true; int currentHeight = popupWindow.Height; MovementAnimation windowAnimation = new MovementAnimation(popupWindow) { From = new Rectangle(popupWindow.X, popupWindow.Y, popupWindow.Width, currentHeight), To = new Rectangle(popupWindow.X, popupWindow.Y, popupWindow.Width, 1), Duration = 8, EasingType = EasingType.Sine, EasingDirection = EasingDirection.In }; MovementAnimation tableAnimation = null; if (popupTable != null) { tableAnimation = new MovementAnimation(popupTable) { From = new Rectangle(popupTable.X, popupTable.Y, popupTable.Width, currentHeight), To = new Rectangle(popupTable.X, popupTable.Y, popupTable.Width, 1), Duration = 8, EasingType = EasingType.Sine, EasingDirection = EasingDirection.In }; } windowAnimation.Completed = () => { if (popupTable != null) { WM.RemoveWindow(popupTable); popupTable = null; } if (popupWindow != null) { WM.RemoveWindow(popupWindow); popupWindow = null; } Expanded = false; closeAnimationRunning = false; Render(); }; windowAnimation.Start(); tableAnimation?.Start(); } private void PopupSelected(int index) { if (index < 0 || index >= Items.Count) { return; } _selectedIndex = index; Collapse(); Render(); SelectionChanged?.Invoke(_selectedIndex, Items[_selectedIndex]); } private void SyncPopupSelection() { if (popupTable != null) { popupTable.SelectedCellIndex = _selectedIndex; } } private void RebuildPopup() { if (!Expanded) { return; } if (popupTable != null) { WM.RemoveWindow(popupTable); popupTable = null; } if (popupWindow != null) { WM.RemoveWindow(popupWindow); popupWindow = null; } int visibleItems = Math.Max(1, Math.Min(MaxVisibleItems, Math.Max(1, Items.Count))); int popupHeight = Math.Max(24, visibleItems * 22); popupWindow = new Window(Process, hostWindow, X, Y + Height, Width, 1); popupWindow.Clear(UITheme.Surface); WM.AddWindow(popupWindow); popupTable = new Table(popupWindow, 0, 0, popupWindow.Width, popupHeight); popupTable.AllowDeselection = false; popupTable.CellHeight = 22; popupTable.Background = UITheme.Surface; popupTable.Foreground = Foreground; popupTable.Border = Border; popupTable.SelectedBackground = UITheme.AccentLight; popupTable.SelectedBorder = Highlight; popupTable.SelectedForeground = Foreground; popupTable.TableCellSelected = PopupSelected; popupTable.OnUnfocused = Collapse; popupWindow.OnUnfocused = Collapse; for (int i = 0; i < Items.Count; i++) { popupTable.Cells.Add(new TableCell(Items[i], i)); } popupTable.SelectedCellIndex = _selectedIndex; popupTable.Render(); WM.AddWindow(popupTable); MovementAnimation windowAnimation = new MovementAnimation(popupWindow) { From = new Rectangle(popupWindow.X, popupWindow.Y, popupWindow.Width, 1), To = new Rectangle(popupWindow.X, popupWindow.Y, popupWindow.Width, popupHeight), Duration = 8, EasingType = EasingType.Sine, EasingDirection = EasingDirection.Out }; MovementAnimation tableAnimation = new MovementAnimation(popupTable) { From = new Rectangle(popupTable.X, popupTable.Y, popupTable.Width, 1), To = new Rectangle(popupTable.X, popupTable.Y, popupTable.Width, popupHeight), Duration = 8, EasingType = EasingType.Sine, EasingDirection = EasingDirection.Out }; windowAnimation.Completed = () => { if (popupWindow != null) { popupWindow.Clear(UITheme.Surface); popupWindow.DrawRectangle(0, 0, popupWindow.Width, popupWindow.Height, Border); WM.Update(popupWindow); } if (popupTable != null) { popupTable.MoveAndResize(0, 0, popupWindow.Width, popupWindow.Height, sendWMEvent: false); popupTable.Render(); } }; windowAnimation.Start(); tableAnimation.Start(); } internal override void Render() { Clear(Background); DrawFilledRectangle(0, 0, Width, Height, Background); Color frame = Expanded ? Highlight : Border; DrawRectangle(0, 0, Width, Height, frame); string displayText = SelectedIndex >= 0 ? SelectedText : PlaceholderText; Color textColor = SelectedIndex >= 0 ? Foreground : UITheme.TextSecondary; int maxChars = Math.Max(1, (Width - 24) / 8); if (displayText.Length > maxChars) { displayText = displayText.Substring(0, Math.Max(1, maxChars - 1)) + "~"; } DrawString(displayText, textColor, 6, (Height / 2) - 8); int arrowCenterX = Width - 12; int arrowCenterY = Height / 2; DrawLine(arrowCenterX - 4, arrowCenterY - 2, arrowCenterX, arrowCenterY + 2, textColor); DrawLine(arrowCenterX, arrowCenterY + 2, arrowCenterX + 4, arrowCenterY - 2, textColor); if (Expanded && popupWindow != null) { popupWindow.MoveAndResize(X, Y + Height, Width, popupWindow.Height, sendWMEvent: false); if (popupTable != null) { popupTable.MoveAndResize(0, 0, popupWindow.Width, popupWindow.Height, sendWMEvent: false); popupTable.Render(); } popupWindow.Clear(UITheme.Surface); popupWindow.DrawRectangle(0, 0, popupWindow.Width, popupWindow.Height, Border); WM.Update(popupWindow); } WM.Update(this); } } }