From 7187e7464f16a9d2991ba2da3c672fdb3cf5de72 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Wed, 16 Jul 2025 13:13:38 +0300 Subject: feat: add Fyne GUI mode with interactive flashcard management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add --gui flag to launch interactive GUI mode - Implement word navigation with prev/next buttons through existing cards - Add delete functionality to remove unwanted flashcards - Add fine-grained regeneration (image-only, audio-only, or both) - Implement audio playback using mpg123 on Linux - Auto-load first word on startup if cards exist - Save translation files for navigation persistence - Use DALL-E 2 with 512x512 images (half size) - Update audio speed to 0.9 (from 0.8) - Add comprehensive GUI documentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- internal/gui/widgets.go | 122 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 internal/gui/widgets.go (limited to 'internal/gui/widgets.go') diff --git a/internal/gui/widgets.go b/internal/gui/widgets.go new file mode 100644 index 0000000..eb9818c --- /dev/null +++ b/internal/gui/widgets.go @@ -0,0 +1,122 @@ +package gui + +import ( + "fmt" + "image" + _ "image/gif" + _ "image/jpeg" + _ "image/png" + "os" + "path/filepath" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" +) + +// ImageDisplay is a custom widget for displaying images +type ImageDisplay struct { + widget.BaseWidget + + container *fyne.Container + imageCanvas *canvas.Image + imageLabel *widget.Label + + currentImage string +} + +// NewImageDisplay creates a new image display widget +func NewImageDisplay() *ImageDisplay { + d := &ImageDisplay{} + + // Create image canvas + d.imageCanvas = canvas.NewImageFromResource(nil) + d.imageCanvas.FillMode = canvas.ImageFillContain + d.imageCanvas.SetMinSize(fyne.NewSize(200, 150)) // Half the size + + // Create label + d.imageLabel = widget.NewLabel("No image") + d.imageLabel.Alignment = fyne.TextAlignCenter + + // Create main container - no navigation buttons here + d.container = container.NewBorder( + nil, + d.imageLabel, + nil, nil, + d.imageCanvas, + ) + + d.ExtendBaseWidget(d) + return d +} + +// CreateRenderer implements fyne.Widget +func (d *ImageDisplay) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(d.container) +} + +// SetImage sets a single image to display +func (d *ImageDisplay) SetImage(imagePath string) { + if imagePath == "" { + d.Clear() + return + } + + d.currentImage = imagePath + + // Load image from file + file, err := os.Open(imagePath) + if err != nil { + d.imageLabel.SetText(fmt.Sprintf("Error loading image: %v", err)) + return + } + defer file.Close() + + img, _, err := image.Decode(file) + if err != nil { + d.imageLabel.SetText(fmt.Sprintf("Error decoding image: %v", err)) + return + } + + // Update canvas + d.imageCanvas.Image = img + d.imageCanvas.Refresh() + + // Update label + d.imageLabel.SetText(filepath.Base(imagePath)) +} + +// SetImages sets multiple images but only displays the first one +func (d *ImageDisplay) SetImages(images []string) { + if len(images) > 0 { + d.SetImage(images[0]) + } else { + d.Clear() + } +} + +// Clear clears the display +func (d *ImageDisplay) Clear() { + d.currentImage = "" + d.imageCanvas.Image = nil + d.imageCanvas.Refresh() + d.imageLabel.SetText("No image") +} + + +// ResourceFromPath creates a Fyne resource from a file path +func ResourceFromPath(path string) (fyne.Resource, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + return fyne.NewStaticResource(filepath.Base(path), data), nil +} \ No newline at end of file -- cgit v1.2.3