Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/card (retry) #1263

Merged
merged 19 commits into from
Sep 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/fyne_demo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func welcomeScreen(a fyne.App) fyne.CanvasObject {
),
layout.NewSpacer(),

widget.NewGroup("Theme",
widget.NewCardContainer("Choose theme", "",
fyne.NewContainerWithLayout(layout.NewGridLayout(2),
widget.NewButton("Dark", func() {
a.Settings().SetTheme(theme.DarkTheme())
Expand Down
2 changes: 1 addition & 1 deletion cmd/fyne_demo/screens/advanced.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func setScaleText(obj *widget.Label, win fyne.Window) {
func AdvancedScreen(win fyne.Window) fyne.CanvasObject {
scale := widget.NewLabel("")

screen := widget.NewGroup("Screen", widget.NewForm(
screen := widget.NewCardContainer("Screen info", "", widget.NewForm(
&widget.FormItem{Text: "Scale", Widget: scale},
))

Expand Down
12 changes: 12 additions & 0 deletions cmd/fyne_demo/screens/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
func ContainerScreen() fyne.CanvasObject {
return widget.NewTabContainer(
widget.NewTabItem("Accordion", makeAccordionTab()),
widget.NewTabItem("Card", makeCardTab()),
widget.NewTabItem("Split", makeSplitTab()),
widget.NewTabItem("Scroll", makeScrollTab()),
// layouts
Expand Down Expand Up @@ -81,6 +82,17 @@ func makeButtonList(count int) []fyne.CanvasObject {
return items
}

func makeCardTab() fyne.CanvasObject {
card1 := widget.NewCardContainer("Book a table", "Which time suits?",
widget.NewRadio([]string{"6:30pm", "7:00pm", "7:45pm"}, func(string) {}))
card2 := widget.NewCardContainer("With media", "No content, with image", nil)
card2.Image = canvas.NewImageFromResource(theme.FyneLogo())
card3 := widget.NewCardContainer("Title 3", "Subtitle", widget.NewCheck("Check me", func(bool) {}))
card4 := widget.NewCardContainer("Title 4", "Another card", widget.NewLabel("Content"))
return fyne.NewContainerWithLayout(layout.NewGridLayout(3), widget.NewVBox(card1, card4),
widget.NewVBox(card2), widget.NewVBox(card3))
stuartmscott marked this conversation as resolved.
Show resolved Hide resolved
}

func makeCell() fyne.CanvasObject {
rect := canvas.NewRectangle(&color.NRGBA{128, 128, 128, 255})
rect.SetMinSize(fyne.NewSize(30, 30))
Expand Down
12 changes: 6 additions & 6 deletions cmd/fyne_demo/screens/window.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ func showText(f fyne.URIReadCloser) {
w.Show()
}

func loadDialogGroup(win fyne.Window) *widget.Group {
return widget.NewGroup("Dialogs",
func loadDialogGroup(win fyne.Window) *widget.CardContainer {
return widget.NewCardContainer("Dialogs", "", widget.NewVBox(
widget.NewButton("Info", func() {
dialog.ShowInformation("Information", "You should know this thing...", win)
}),
Expand Down Expand Up @@ -182,11 +182,11 @@ func loadDialogGroup(win fyne.Window) *widget.Group {
},
win)
}),
)
))
}

func loadWindowGroup() fyne.Widget {
windowGroup := widget.NewGroup("Windows",
windowGroup := widget.NewVBox(
widget.NewButton("New window", func() {
w := fyne.CurrentApp().NewWindow("Hello")
w.SetContent(widget.NewLabel("Hello World!"))
Expand Down Expand Up @@ -224,15 +224,15 @@ func loadWindowGroup() fyne.Widget {
}))
}

otherGroup := widget.NewGroup("Other",
otherGroup := widget.NewCardContainer("Other", "",
widget.NewButton("Notification", func() {
fyne.CurrentApp().SendNotification(&fyne.Notification{
Title: "Fyne Demo",
Content: "Testing notifications...",
})
}))

return widget.NewVBox(windowGroup, otherGroup)
return widget.NewVBox(widget.NewCardContainer("Windows", "", windowGroup), otherGroup)
}

// DialogScreen loads a panel that lists the dialog windows that can be tested.
Expand Down
1 change: 1 addition & 0 deletions internal/widget/shadow.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type ElevationLevel int
// https://storage.googleapis.com/spec-host/mio-staging%2Fmio-design%2F1584058305895%2Fassets%2F0B6xUSjjSulxceF9udnA4Sk5tdU0%2Fbaselineelevation-chart.png
const (
BaseLevel ElevationLevel = 0
CardLevel ElevationLevel = 1
ButtonLevel ElevationLevel = 2
MenuLevel ElevationLevel = 4
PopUpLevel ElevationLevel = 8
Expand Down
214 changes: 214 additions & 0 deletions widget/cardcontainer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package widget

import (
"fyne.io/fyne"
"fyne.io/fyne/canvas"
"fyne.io/fyne/internal/widget"
"fyne.io/fyne/theme"
)

// CardContainer widget groups title, subtitle with content and a header image
type CardContainer struct {
BaseWidget
Title, Subtitle string
Image *canvas.Image
Content fyne.CanvasObject
}

// NewCardContainer creates a new card widget with the specified title, subtitle and content (all optional).
func NewCardContainer(title, subtitle string, content fyne.CanvasObject) *CardContainer {
card := &CardContainer{
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 *CardContainer) CreateRenderer() fyne.WidgetRenderer {
c.ExtendBaseWidget(c)

header := canvas.NewText(c.Title, theme.TextColor())
header.TextStyle.Bold = true
subHeader := canvas.NewText(c.Subtitle, theme.TextColor())

objects := []fyne.CanvasObject{header, subHeader}
if c.Image != nil {
objects = append(objects, c.Image)
}
if c.Content != nil {
objects = append(objects, c.Content)
}
stuartmscott marked this conversation as resolved.
Show resolved Hide resolved
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 *CardContainer) MinSize() fyne.Size {
c.ExtendBaseWidget(c)
return c.BaseWidget.MinSize()
}

// SetContent changes the body of this card to have the specified content.
func (c *CardContainer) SetContent(obj fyne.CanvasObject) {
c.Content = obj

c.Refresh()
}

// SetImage changes the image displayed above the title for this card.
func (c *CardContainer) SetImage(img *canvas.Image) {
c.Image = img

c.Refresh()
}

// SetSubTitle updates the secondary title for this card.
func (c *CardContainer) SetSubTitle(text string) {
c.Subtitle = text

c.Refresh()
}

// SetTitle updates the main title for this card.
func (c *CardContainer) SetTitle(text string) {
c.Title = text

c.Refresh()
}

type cardRenderer struct {
*widget.ShadowingRenderer

header, subHeader *canvas.Text

card *CardContainer
}

const (
cardMediaHeight = 128
stuartmscott marked this conversation as resolved.
Show resolved Hide resolved
)

// 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()))
stuartmscott marked this conversation as resolved.
Show resolved Hide resolved
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
}

titlePad := theme.Padding() * 2
contentPad := theme.Padding()
size.Width -= titlePad * 2
pos.X += titlePad
pos.Y += theme.Padding()

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 += titlePad
pos.X -= contentPad
pos.Y += contentPad

if c.card.Content != nil {
height := size.Height - pos.Y - titlePad - contentPad
c.card.Content.Move(pos)
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 {
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())
}

titlePad := theme.Padding() * 2
contentPad := theme.Padding()
min := fyne.NewSize(titlePad*2+theme.Padding(), titlePad*2+theme.Padding())
if hasImage {
min = fyne.NewSize(min.Width, min.Height+cardMediaHeight)
}

if hasHeader {
min = fyne.NewSize(fyne.Max(min.Width, c.header.MinSize().Width+titlePad*2+theme.Padding()),
min.Height+c.header.MinSize().Height)
if !hasSubHeader && !hasContent {
min.Height -= titlePad
}
}
if hasSubHeader {
min = fyne.NewSize(fyne.Max(min.Width, c.subHeader.MinSize().Width+titlePad*2+theme.Padding()),
min.Height+c.subHeader.MinSize().Height)
if !hasHeader && !hasContent {
min.Height -= titlePad
}
}
if hasContent {
min = fyne.NewSize(fyne.Max(min.Width, c.card.Content.MinSize().Width+contentPad*2+theme.Padding()),
min.Height+c.card.Content.MinSize().Height)
}

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())
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 = int(float32(theme.TextSize()) * 1.7)
c.header.Color = theme.TextColor()
}
if c.subHeader != nil {
c.subHeader.TextSize = theme.TextSize()
c.subHeader.Color = theme.TextColor()
}
}
Loading