package sidebar
import (
"context"
"log/slog"
"strings"
"github.com/diamondburned/arikawa/v3/discord"
"github.com/diamondburned/gotk4-adwaita/pkg/adw"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
"github.com/diamondburned/gotkit/gtkutil"
"github.com/diamondburned/gotkit/gtkutil/cssutil"
"libdb.so/dissent/internal/gtkcord"
"libdb.so/dissent/internal/sidebar/channels"
"libdb.so/dissent/internal/sidebar/direct"
"libdb.so/dissent/internal/sidebar/directbutton"
"libdb.so/dissent/internal/sidebar/guilds"
)
type Sidebar struct {
*gtk.Box
Left *gtk.Box
DMView *directbutton.View
Guilds *guilds.View
Right *gtk.Stack
current struct {
w gtk.Widgetter
}
placeholder gtk.Widgetter
ctx context.Context
}
var sidebarCSS = cssutil.Applier("sidebar-sidebar", `
@define-color sidebar_bg mix(@borders, @theme_bg_color, 0.25);
.sidebar-guildside {
background-color: @sidebar_bg;
}
.sidebar-guildside windowcontrols:not(.empty) {
margin-left: 4px;
margin-right: 4px;
}
.sidebar-guildside windowcontrols:not(.empty) button {
margin: 0px 0;
}
`)
func NewSidebar(ctx context.Context) *Sidebar {
s := Sidebar{
ctx: ctx,
}
s.Guilds = guilds.NewView(ctx)
s.Guilds.Invalidate()
s.DMView = directbutton.NewView(ctx)
s.DMView.Invalidate()
dmSeparator := gtk.NewSeparator(gtk.OrientationHorizontal)
dmSeparator.AddCSSClass("sidebar-dm-separator")
leftBox := gtk.NewBox(gtk.OrientationVertical, 0)
leftBox.Append(s.DMView)
leftBox.Append(dmSeparator)
leftBox.Append(s.Guilds)
leftScroll := gtk.NewScrolledWindow()
leftScroll.SetVExpand(true)
leftScroll.SetPolicy(gtk.PolicyNever, gtk.PolicyExternal)
leftScroll.SetChild(leftBox)
leftCtrl := gtk.NewWindowControls(gtk.PackStart)
leftCtrl.SetHAlign(gtk.AlignCenter)
s.Left = gtk.NewBox(gtk.OrientationVertical, 0)
s.Left.AddCSSClass("sidebar-guildside")
s.Left.Append(leftCtrl)
s.Left.Append(leftScroll)
s.placeholder = gtk.NewWindowHandle()
s.Right = gtk.NewStack()
s.Right.SetSizeRequest(channels.ChannelsWidth, -1)
s.Right.SetVExpand(true)
s.Right.SetHExpand(true)
s.Right.AddChild(s.placeholder)
s.Right.SetVisibleChild(s.placeholder)
s.Right.SetTransitionType(gtk.StackTransitionTypeCrossfade)
userBar := newUserBar(ctx, []gtkutil.PopoverMenuItem{
gtkutil.MenuItem("Quick Switcher", "win.quick-switcher"),
gtkutil.MenuSeparator("User Settings"),
gtkutil.Submenu("Set _Status", []gtkutil.PopoverMenuItem{
gtkutil.MenuItem("_Online", "win.set-online"),
gtkutil.MenuItem("_Idle", "win.set-idle"),
gtkutil.MenuItem("_Do Not Disturb", "win.set-dnd"),
gtkutil.MenuItem("In_visible", "win.set-invisible"),
}),
gtkutil.MenuSeparator(""),
gtkutil.MenuItem("_Preferences", "app.preferences"),
gtkutil.MenuItem("_About", "app.about"),
gtkutil.MenuItem("_Logs", "app.logs"),
gtkutil.MenuItem("_Quit", "app.quit"),
})
rightWrap := adw.NewToolbarView()
rightWrap.AddBottomBar(userBar)
rightWrap.SetContent(s.Right)
s.Box = gtk.NewBox(gtk.OrientationHorizontal, 0)
s.Box.SetHExpand(false)
s.Box.Append(s.Left)
s.Box.Append(rightWrap)
sidebarCSS(s)
return &s
}
func (s *Sidebar) GuildID() discord.GuildID {
ch, ok := s.current.w.(*channels.View)
if !ok {
return 0
}
return ch.GuildID()
}
func (s *Sidebar) stackSelect(w gtk.Widgetter) {
if w == s.current.w {
return
}
old := s.current.w
s.current.w = w
if w == nil {
s.Right.SetVisibleChild(s.placeholder)
} else {
s.Right.AddChild(w)
s.Right.SetVisibleChild(w)
w := gtk.BaseWidget(w)
w.GrabFocus()
}
if old != nil {
gtkutil.NotifyProperty(s.Right, "transition-running", func() bool {
if !s.Right.TransitionRunning() {
s.Right.Remove(old)
w := gtk.BaseWidget(old)
slog.Debug(
"sidebar: right stack transition done, removed widget",
"widget", w.Type().String()+"."+strings.Join(w.CSSClasses(), "."))
return true
} else {
slog.Debug("sidebar: right stack transition started")
return false
}
})
}
}
func (s *Sidebar) OpenDMs() *direct.ChannelView {
if direct, ok := s.current.w.(*direct.ChannelView); ok {
return direct
}
s.unselect()
s.DMView.SetSelected(true)
direct := direct.NewChannelView(s.ctx)
direct.SetVExpand(true)
direct.Invalidate()
s.stackSelect(direct)
return direct
}
func (s *Sidebar) openGuild(guildID discord.GuildID) *channels.View {
chs, ok := s.current.w.(*channels.View)
if ok && chs.GuildID() == guildID {
return chs
}
s.unselect()
s.Guilds.SetSelectedGuild(guildID)
chs = channels.NewView(s.ctx, guildID)
chs.SetVExpand(true)
chs.InvalidateHeader()
s.stackSelect(chs)
return chs
}
func (s *Sidebar) unselect() {
s.Guilds.Unselect()
s.DMView.Unselect()
s.stackSelect(nil)
}
func (s *Sidebar) Unselect() {
s.unselect()
s.Right.SetVisibleChild(s.placeholder)
}
func (s *Sidebar) SetSelectedGuild(guildID discord.GuildID) {
s.Guilds.SetSelectedGuild(guildID)
s.openGuild(guildID)
}
func (s *Sidebar) SelectChannel(chID discord.ChannelID) {
state := gtkcord.FromContext(s.ctx)
ch, _ := state.Cabinet.Channel(chID)
if ch == nil {
slog.Error(
"cannot select channel in sidebar since it's not found in state",
"channel_id", chID)
return
}
s.Guilds.SetSelectedGuild(ch.GuildID)
if ch.GuildID.IsValid() {
guild := s.openGuild(ch.GuildID)
guild.SelectChannel(chID)
} else {
direct := s.OpenDMs()
direct.SelectChannel(chID)
}
}