| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- package widget
- import (
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/canvas"
- "fyne.io/fyne/v2/internal/widget"
- "fyne.io/fyne/v2/theme"
- )
- // Card widget groups title, subtitle with content and a header image
- //
- // Since: 1.4
- type Card struct {
- BaseWidget
- Title, Subtitle string
- Image *canvas.Image
- Content fyne.CanvasObject
- }
- // NewCard creates a new card widget with the specified title, subtitle and content (all optional).
- //
- // Since: 1.4
- func NewCard(title, subtitle string, content fyne.CanvasObject) *Card {
- card := &Card{
- Title: title,
- Subtitle: subtitle,
- Content: content,
- }
- card.ExtendBaseWidget(card)
- return card
- }
- // CreateRenderer is a private method to Fyne which links this widget to its renderer
- func (c *Card) CreateRenderer() fyne.WidgetRenderer {
- c.ExtendBaseWidget(c)
- header := canvas.NewText(c.Title, theme.ForegroundColor())
- header.TextStyle.Bold = true
- subHeader := canvas.NewText(c.Subtitle, theme.ForegroundColor())
- objects := []fyne.CanvasObject{header, subHeader}
- if c.Image != nil {
- objects = append(objects, c.Image)
- }
- if c.Content != nil {
- objects = append(objects, c.Content)
- }
- r := &cardRenderer{widget.NewShadowingRenderer(objects, widget.CardLevel),
- header, subHeader, c}
- r.applyTheme()
- return r
- }
- // MinSize returns the size that this widget should not shrink below
- func (c *Card) MinSize() fyne.Size {
- c.ExtendBaseWidget(c)
- return c.BaseWidget.MinSize()
- }
- // SetContent changes the body of this card to have the specified content.
- func (c *Card) SetContent(obj fyne.CanvasObject) {
- c.Content = obj
- c.Refresh()
- }
- // SetImage changes the image displayed above the title for this card.
- func (c *Card) SetImage(img *canvas.Image) {
- c.Image = img
- c.Refresh()
- }
- // SetSubTitle updates the secondary title for this card.
- func (c *Card) SetSubTitle(text string) {
- c.Subtitle = text
- c.Refresh()
- }
- // SetTitle updates the main title for this card.
- func (c *Card) SetTitle(text string) {
- c.Title = text
- c.Refresh()
- }
- type cardRenderer struct {
- *widget.ShadowingRenderer
- header, subHeader *canvas.Text
- card *Card
- }
- const (
- cardMediaHeight = 128
- )
- // Layout the components of the card container.
- func (c *cardRenderer) Layout(size fyne.Size) {
- pos := fyne.NewPos(theme.Padding()/2, theme.Padding()/2)
- size = size.Subtract(fyne.NewSize(theme.Padding(), theme.Padding()))
- c.LayoutShadow(size, pos)
- if c.card.Image != nil {
- c.card.Image.Move(pos)
- c.card.Image.Resize(fyne.NewSize(size.Width, cardMediaHeight))
- pos.Y += cardMediaHeight
- }
- contentPad := theme.Padding()
- if c.card.Title != "" || c.card.Subtitle != "" {
- titlePad := theme.Padding() * 2
- size.Width -= titlePad * 2
- pos.X += titlePad
- pos.Y += titlePad
- if c.card.Title != "" {
- height := c.header.MinSize().Height
- c.header.Move(pos)
- c.header.Resize(fyne.NewSize(size.Width, height))
- pos.Y += height + theme.Padding()
- }
- if c.card.Subtitle != "" {
- height := c.subHeader.MinSize().Height
- c.subHeader.Move(pos)
- c.subHeader.Resize(fyne.NewSize(size.Width, height))
- pos.Y += height + theme.Padding()
- }
- size.Width = size.Width + titlePad*2
- pos.X = pos.X - titlePad
- pos.Y += titlePad
- }
- size.Width -= contentPad * 2
- pos.X += contentPad
- if c.card.Content != nil {
- height := size.Height - contentPad*2 - (pos.Y - theme.Padding()/2) // adjust for content and initial offset
- if c.card.Title != "" || c.card.Subtitle != "" {
- height += contentPad
- pos.Y -= contentPad
- }
- c.card.Content.Move(pos.Add(fyne.NewPos(0, contentPad)))
- c.card.Content.Resize(fyne.NewSize(size.Width, height))
- }
- }
- // MinSize calculates the minimum size of a card.
- // This is based on the contained text, image and content.
- func (c *cardRenderer) MinSize() fyne.Size {
- hasHeader := c.card.Title != ""
- hasSubHeader := c.card.Subtitle != ""
- hasImage := c.card.Image != nil
- hasContent := c.card.Content != nil
- if !hasHeader && !hasSubHeader && !hasContent { // just image, or nothing
- if c.card.Image == nil {
- return fyne.NewSize(theme.Padding(), theme.Padding()) // empty, just space for border
- }
- return fyne.NewSize(c.card.Image.MinSize().Width+theme.Padding(), cardMediaHeight+theme.Padding())
- }
- contentPad := theme.Padding()
- min := fyne.NewSize(theme.Padding(), theme.Padding())
- if hasImage {
- min = fyne.NewSize(min.Width, min.Height+cardMediaHeight)
- }
- if hasHeader || hasSubHeader {
- titlePad := theme.Padding() * 2
- min = min.Add(fyne.NewSize(0, titlePad*2))
- if hasHeader {
- headerMin := c.header.MinSize()
- min = fyne.NewSize(fyne.Max(min.Width, headerMin.Width+titlePad*2+theme.Padding()),
- min.Height+headerMin.Height)
- if hasSubHeader {
- min.Height += theme.Padding()
- }
- }
- if hasSubHeader {
- subHeaderMin := c.subHeader.MinSize()
- min = fyne.NewSize(fyne.Max(min.Width, subHeaderMin.Width+titlePad*2+theme.Padding()),
- min.Height+subHeaderMin.Height)
- }
- }
- if hasContent {
- contentMin := c.card.Content.MinSize()
- min = fyne.NewSize(fyne.Max(min.Width, contentMin.Width+contentPad*2+theme.Padding()),
- min.Height+contentMin.Height+contentPad*2)
- }
- return min
- }
- func (c *cardRenderer) Refresh() {
- c.header.Text = c.card.Title
- c.header.Refresh()
- c.subHeader.Text = c.card.Subtitle
- c.subHeader.Refresh()
- objects := []fyne.CanvasObject{c.header, c.subHeader}
- if c.card.Image != nil {
- objects = append(objects, c.card.Image)
- }
- if c.card.Content != nil {
- objects = append(objects, c.card.Content)
- }
- c.ShadowingRenderer.SetObjects(objects)
- c.applyTheme()
- c.Layout(c.card.Size())
- c.ShadowingRenderer.RefreshShadow()
- canvas.Refresh(c.card.super())
- }
- // applyTheme updates this button to match the current theme
- func (c *cardRenderer) applyTheme() {
- if c.header != nil {
- c.header.TextSize = theme.TextHeadingSize()
- c.header.Color = theme.ForegroundColor()
- }
- if c.subHeader != nil {
- c.subHeader.TextSize = theme.TextSize()
- c.subHeader.Color = theme.ForegroundColor()
- }
- if c.card.Content != nil {
- c.card.Content.Refresh()
- }
- }
|