Skip to content

Conversation

nervo
Copy link
Contributor

@nervo nervo commented Jun 10, 2024

While i was working on my pr on a scrollbar bubble component (see: charmbracelet/bubbles#536), i suddenly realized how amazing it would be if such a component could be render as a border.

imagine something like that:

╭─────────╮
│Lorem    █
│ipsum    █
│dolor    █
│sit      ░
│amet     ░
╰─────────╯
╭─────────╮
│Lorem    ┴
│ipsum    █
│dolor    █
│sit      ░
│amet     ┬
╰─────────╯
╭──────────╮
│Lorem    ╭┴╮
│ipsum    │█│
│dolor    │█│
│sit      │░│
│amet     ╰┬╯
╰──────────╯

Anyway, i quickly realized too that the way border are handled, there is no room for custom complex rendering...

So i came up with an elegant solution, in multiple steps. The first one would be to introduce a new interface Borderer, and a first non-breaking implementation: NormalRenderer. Then, it's all about transferring border responsabitilities to borderer.

Here is this first step :)

Before going further, and make this interface able to handle complex borders, i would like to know if you're interested in :)

One of the annoying point to fix first, would be - for instance - the incredible heavy interface weight:

type Borderer interface {
	// Get
	GetStyle() Border
	GetTop() bool
	GetRight() bool
	GetBottom() bool
	GetLeft() bool
	GetTopForeground() TerminalColor
	GetRightForeground() TerminalColor
	GetBottomForeground() TerminalColor
	GetLeftForeground() TerminalColor
	GetTopBackground() TerminalColor
	GetRightBackground() TerminalColor
	GetBottomBackground() TerminalColor
	GetLeftBackground() TerminalColor
	GetTopSize() int
	GetRightSize() int
	GetBottomSize() int
	GetLeftSize() int

	// Set
	Style(border Border) Borderer
	Top(v bool) Borderer
	Right(v bool) Borderer
	Bottom(v bool) Borderer
	Left(v bool) Borderer
	TopForeground(c TerminalColor) Borderer
	RightForeground(c TerminalColor) Borderer
	BottomForeground(c TerminalColor) Borderer
	LeftForeground(c TerminalColor) Borderer
	TopBackground(c TerminalColor) Borderer
	RightBackground(c TerminalColor) Borderer
	BottomBackground(c TerminalColor) Borderer
	LeftBackground(c TerminalColor) Borderer

	// Unset
	UnsetStyle() Borderer
	UnsetTop() Borderer
	UnsetRight() Borderer
	UnsetBottom() Borderer
	UnsetLeft() Borderer
	UnsetTopForeground() Borderer
	UnsetRightForeground() Borderer
	UnsetBottomForeground() Borderer
	UnsetLeftForeground() Borderer
	UnsetTopBackground() Borderer
	UnsetRightBackground() Borderer
	UnsetBottomBackground() Borderer
	UnsetLeftBackground() Borderer

	Apply(str string) string
}

Somehow related:

@nervo nervo requested review from meowgorithm and muesli as code owners June 10, 2024 14:02
@nervo nervo force-pushed the border-interface branch from bc2473a to ae651ee Compare June 10, 2024 14:15
@meowgorithm
Copy link
Member

@nervo This is SO RAD. We're definitely open to it. Could you share what a Borderer implementation would like (if it's not too much work)?

@nervo
Copy link
Contributor Author

nervo commented Jun 12, 2024

@meowgorithm i'm glad to have your attention :)

So, well, combining at the same time the both constraints of lighten the interface, and give a way to customize border of any direction (top, right, bottom, left), i was thinking about a notion of Region.

A borderer object could have 4 main methods to get 4 regions of 4 directions:

  • TopRegion
  • RightRegion
  • BottomRegion
  • ... (you get it :) )

Each of this regions (also interfaces) should then be able to respond to the current methods of:

  • get/set/unset their presence
  • get/set/unset their foreground and background
  • get their size

Ultimately, a region should have a Render method, with the size and maybe the direction as parameter.

This way, something like that could be possible:

style := lipgloss.NewStyle().
	Borderer(
		NewBorderer(
			NewTitleRegion("This is my amazing and far too long title"), // Top: a title
			NewScrollbarRegion(...), // Right: a scrollbar (linked to a viewport, for instance)
			NewRegion(), // Bottom: Nothing special, a traditionnal border
			NewRegion(), // Left: Nothing special, a traditionnal border too
		)
	)

What do you think ?

@meowgorithm
Copy link
Member

Okay this is cool. To properly understand it a full, runnable example would be really helpful.

Does charmbracelet/bubbles#536 use this API?

@bashbunni
Copy link
Contributor

Hey @nervo if you have a working example of this, it would be great to test out!

@bashbunni
Copy link
Contributor

bashbunni commented Jan 22, 2025

Hey @nervo pinging again to see if you have a working example of this feature

@lrstanley
Copy link

In case it gives any inspiration, I am using a solution that is similar to what is described (not implementation, but functionality), in one of my projects. It's quite crude, likely needs a bit more work on the performance side, and the code is thrown together, but it supports top & bottom left/center/right (nothing for sides). Also supports gradient borders. You can find the code here, and some of the snapshot tests to show what some of the layouts would look like. Example of it being used (really only in 2 places, but set dynamically if the provided value implements those specific interfaces I use):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants