cchat-gtk/internal/ui/service/session/server/button/button.go

247 lines
6.1 KiB
Go

package button
import (
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/roundimage"
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
"github.com/gotk3/gotk3/gtk"
)
const UnreadColorDefs = `
@define-color mentioned rgb(240, 71, 71);
`
const IconSize = 38
type ToggleButton struct {
gtk.ToggleButton
Label *rich.Label
labelRev *gtk.Revealer
// These fields are nil if image is false.
Image *roundimage.StillImage
imageRev *gtk.Revealer
Box *gtk.Box
state rich.LabelStateStorer
clicked func(bool)
readcss primitives.ClassEnum
icon string // whether or not the button has an icon
label bool
}
var serverButtonCSS = primitives.PrepareClassCSS("server-button", `
.server-button {
min-width: 0px;
}
.selected-server {
border-left: 2px solid mix(@theme_base_color, @theme_fg_color, 0.1);
background-color: mix(@theme_base_color, @theme_fg_color, 0.1);
color: @theme_fg_color;
}
.read {
/* color: alpha(@theme_fg_color, 0.5); */
border-left: 2px solid transparent;
}
.unread {
color: @theme_fg_color;
border-left: 2px solid alpha(@theme_fg_color, 0.75);
/* background-color: alpha(@theme_fg_color, 0.05); */
}
.mentioned {
color: @mentioned;
border-left: 2px solid alpha(@mentioned, 0.75);
background-color: alpha(@mentioned, 0.05);
}
`+UnreadColorDefs)
// NewToggleButton creates a new toggle button.
func NewToggleButton(state rich.LabelStateStorer) *ToggleButton {
label := rich.NewLabelWithRenderer(state, rich.RenderSkipImages)
label.SetMarginStart(5)
label.Show()
labelRev, _ := gtk.RevealerNew()
labelRev.Add(label)
labelRev.SetRevealChild(true)
labelRev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
labelRev.SetTransitionDuration(100)
labelRev.Show()
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
box.SetHAlign(gtk.ALIGN_START)
box.PackStart(labelRev, false, false, 0)
box.Show()
button, _ := gtk.ToggleButtonNew()
button.SetRelief(gtk.RELIEF_NONE)
button.Add(box)
button.Show()
tb := &ToggleButton{
ToggleButton: *button,
Label: label,
labelRev: labelRev,
Box: box,
state: state,
clicked: func(bool) {},
}
tb.SetShowLabel(true)
tb.Connect("clicked", func(w *gtk.ToggleButton) { tb.clicked(w.GetActive()) })
serverButtonCSS(tb)
// Ensure that we display an icon when we receive one.
state.OnUpdate(func() {
if state.Image().HasImage() {
tb.ensureImage()
}
})
return tb
}
// SetSelected sets the button's intermediate state and appearance to look
// like it's clicked without triggering the callback.
func (b *ToggleButton) SetSelected(selected bool) {
if selected {
primitives.AddClass(b, "selected-server")
} else {
primitives.RemoveClass(b, "selected-server")
}
// Some special edge case that I forgot.
if !selected {
b.SetActive(false)
}
}
// SetShowLabel sets whether or not to show the button's label. If the button
// does not have an image, then the label is always shown.
func (b *ToggleButton) SetShowLabel(showLabel bool) {
b.label = showLabel
// Enforce particular rules that are unfeasible at the moment. When these
// conditions change elsewhere, this function will be called again.
if b.imageRev != nil {
showLabel = showLabel || !b.imageRev.GetRevealChild()
} else {
showLabel = true
}
// Expand the box when we're showing the label.
b.SetHExpand(showLabel)
b.labelRev.SetRevealChild(showLabel)
}
// GetShowLabel gets whether or not the label is being shown.
func (b *ToggleButton) GetShowLabel() bool {
return b.labelRev.GetRevealChild() || b.label
}
// SetClicked sets the callback to run when clicked. It overrides the previous
// callback.
func (b *ToggleButton) SetClicked(clicked func(bool)) {
b.clicked = clicked
}
func (b *ToggleButton) SetClickedIfTrue(clickedIfTrue func()) {
b.clicked = func(clicked bool) {
if clicked {
clickedIfTrue()
}
}
}
func (b *ToggleButton) ensureImage() {
if b.Image != nil {
return
}
b.Image = roundimage.NewStillImage(b, 0)
b.Image.SetSizeRequest(IconSize, IconSize)
b.Image.Show()
// TODO: tooltip false once hover is implemented.
rich.BindRoundImage(b.Image, b.state, true)
b.imageRev, _ = gtk.RevealerNew()
b.imageRev.Add(b.Image)
b.imageRev.SetRevealChild(true)
b.imageRev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
b.imageRev.SetTransitionDuration(75)
b.imageRev.Show()
b.Box.PackStart(b.imageRev, false, false, 0)
b.Box.ReorderChild(b.imageRev, 0)
// Set the callback to render the user's initials if there is no name.
b.Image.UseInitialsIfNone(func() string {
return b.state.Label().String()
})
// Restore the label's visible state now that we have an image.
b.SetShowLabel(b.label)
}
// UseEmptyIcon forces the ToggleButton to show an icon, even if it's a
// placeholder.
func (b *ToggleButton) UseEmptyIcon() {
b.ensureImage()
}
// SetNormal sets the button's state to normal from either loading or failed.
func (b *ToggleButton) SetNormal() {
b.Label.SetRenderer(rich.RenderSkipImages)
b.SetSensitive(true)
if b.Image != nil && b.icon != "" {
b.SetPlaceholderIcon(b.icon, b.Image.Size())
}
}
// SetLoading sets the button's state to loading.
func (b *ToggleButton) SetLoading() {
b.Label.SetRenderer(rich.RenderSkipImages)
b.SetSensitive(false)
if b.Image != nil && b.icon != "" {
b.SetPlaceholderIcon("content-loading-symbolic", b.Image.Size())
}
}
func (b *ToggleButton) SetFailed(err error, retry func()) {
b.Label.SetRenderer(rich.MakeRed)
b.SetSensitive(true)
// If we have an icon set, then we can use the failed icon.
if b.Image != nil && b.icon != "" {
b.SetPlaceholderIcon("computer-fail-symbolic", b.Image.Size())
}
}
func (b *ToggleButton) SetUnreadUnsafe(unread, mentioned bool) {
switch {
// Prioritize mentions over unreads.
case mentioned:
b.readcss.SetClass(b, "mentioned")
case unread:
b.readcss.SetClass(b, "unread")
default:
b.readcss.SetClass(b, "read")
}
}
func (b *ToggleButton) SetPlaceholderIcon(iconName string, iconSzPx int) {
b.icon = iconName
b.ensureImage()
b.Image.SetPlaceholderIcon(iconName, iconSzPx)
}