package uv import "github.com/charmbracelet/x/ansi" // Window represents a rectangular area on the screen. It can be a root window // with no parent, or a sub-window with a parent window. A window can have its // own buffer or share the buffer of its parent window (view). type Window struct { *Buffer method *WidthMethod parent *Window bounds Rectangle } var ( _ Screen = (*Window)(nil) _ Drawable = (*Window)(nil) ) // HasParent returns whether the window has a parent window. This can be used // to determine if the window is a root window or a sub-window. func (w *Window) HasParent() bool { return w.parent != nil } // Parent returns the parent window of the current window. // If the window does not have a parent, it returns nil. func (w *Window) Parent() *Window { return w.parent } // MoveTo moves the window to the specified x and y coordinates. func (w *Window) MoveTo(x, y int) { size := w.bounds.Size() w.bounds.Min.X = x w.bounds.Min.Y = y w.bounds.Max.X = x + size.X w.bounds.Max.Y = y + size.Y } // MoveBy moves the window by the specified delta x and delta y. func (w *Window) MoveBy(dx, dy int) { w.bounds.Min.X += dx w.bounds.Min.Y += dy w.bounds.Max.X += dx w.bounds.Max.Y += dy } // Clone creates an exact copy of the window, including its buffer and values. // The cloned window will have the same parent and method as the original // window. func (w *Window) Clone() *Window { return w.CloneArea(w.Buffer.Bounds()) } // CloneArea creates an exact copy of the window, including its buffer and // values, but only within the specified area. The cloned window will have the // same parent and method as the original window, but its bounds will be // limited to the specified area. func (w *Window) CloneArea(area Rectangle) *Window { clone := new(Window) clone.Buffer = w.Buffer.CloneArea(area) clone.parent = w.parent clone.method = w.method clone.bounds = area return clone } // Resize resizes the window to the specified width and height. func (w *Window) Resize(width, height int) { // Only resize the buffer if this window owns its buffer. if w.parent == nil || w.Buffer != w.parent.Buffer { w.Buffer.Resize(width, height) } w.bounds.Max.X = w.bounds.Min.X + width w.bounds.Max.Y = w.bounds.Min.Y + height } // WidthMethod returns the method used to calculate the width of characters in // the window. func (w *Window) WidthMethod() WidthMethod { return *w.method } // Bounds returns the bounds of the window as a rectangle. func (w *Window) Bounds() Rectangle { return w.bounds } // NewWindow creates a new window with its own buffer relative to the parent // window at the specified position and size. // // This will panic if width or height is negative. func (w *Window) NewWindow(x, y, width, height int) *Window { return newWindow(w, x, y, width, height, w.method, false) } // NewView creates a new view into the parent window at the specified position // and size. Unlike [Window.NewWindow], this view shares the same buffer as the // parent window. func (w *Window) NewView(x, y, width, height int) *Window { return newWindow(w, x, y, width, height, w.method, true) } // NewWindow creates a new root [Window] with the given size and width method. // If the method is nil, it defaults to [ansi.WcWidth]. // // The [WidthMethod] is used to calculate the width of characters in the // window, which is important for correctly rendering text, especially when // dealing with wide characters, combining characters, emojis, and other // Unicode characters that may have varying widths. // // Use [ansi.WcWidth] as the default width method, which is a common // implementation that handles a wide range of Unicode characters according to // the Unicode Standard. Use [ansi.GraphemeWidth] if you know that your // terminal supports grapheme clusters and proper rendering of combining // characters and emojis, and you want to ensure that the width calculations // are based on grapheme clusters rather than individual code points. // // This will panic if width or height is negative. func NewWindow(width, height int, method WidthMethod) *Window { if method == nil { method = ansi.WcWidth } return newWindow(nil, 0, 0, width, height, &method, false) } // SetWidthMethod sets the width method for the window. func (w *Window) SetWidthMethod(method WidthMethod) { w.method = &method } // newWindow creates a new [Window] with the specified parent, position, // method, and size. func newWindow(parent *Window, x, y, width, height int, method *WidthMethod, view bool) *Window { w := new(Window) if view { w.Buffer = parent.Buffer } else { w.Buffer = NewBuffer(width, height) } w.parent = parent w.method = method w.bounds = Rect(x, y, width, height) return w }