github.com/Kintar/etxt@v0.0.0-20221224033739-2fc69f000137/examples/ebiten/all_glyphs/main.go (about)

     1  package main
     2  
     3  import "os"
     4  import "log"
     5  import "fmt"
     6  import "time"
     7  import "image/color"
     8  
     9  import "github.com/hajimehoshi/ebiten/v2"
    10  import "golang.org/x/image/math/fixed"
    11  
    12  import "github.com/Kintar/etxt"
    13  import "github.com/Kintar/etxt/esizer"
    14  
    15  // NOTE: to honor the beauty of fonts, I decided to make this example
    16  //       resizable and fullscreen-able (press F, but release fast or
    17  //       it will re-trigger). You can navigate glyphs with the up/down
    18  //       arrows. You can also press H to switch on/off the current glyph
    19  //       info. Enjoy your fonts!
    20  
    21  const GlyphSize = 64      // controls the glyph size
    22  const GlyphSpacing = 1.24 // controls the space around/between glyphs
    23  
    24  type Game struct {
    25  	txtRenderer   *etxt.Renderer
    26  	numGlyphs     int
    27  	position      int
    28  	spacing       int
    29  	lastPressTime time.Time
    30  	glyphs        []etxt.GlyphIndex
    31  
    32  	screenWidth  int
    33  	screenHeight int
    34  	hint         bool
    35  	hintRenderer *etxt.Renderer
    36  	bottomFade   *ebiten.Image
    37  }
    38  
    39  func NewGame(renderer *etxt.Renderer, font *etxt.Font, noHint bool) *Game {
    40  	numGlyphs := font.NumGlyphs()
    41  	fixedSizer := &esizer.FixedSizer{}
    42  	renderer.SetSizer(fixedSizer)
    43  
    44  	var hintRenderer *etxt.Renderer
    45  	if !noHint {
    46  		hintRenderer = etxt.NewStdRenderer()
    47  		hintRenderer.SetFont(font)
    48  		hintRenderer.SetSizePx(14)
    49  		hintRenderer.SetHorzAlign(etxt.Right)
    50  		cache := etxt.NewDefaultCache(1024 * 1024) // 1MB cache
    51  		hintRenderer.SetCacheHandler(cache.NewHandler())
    52  		hintRenderer.SetColor(color.RGBA{92, 92, 92, 255})
    53  	}
    54  
    55  	game := &Game{
    56  		txtRenderer:   renderer,
    57  		numGlyphs:     numGlyphs,
    58  		position:      0,
    59  		spacing:       0,
    60  		lastPressTime: time.Now(),
    61  		screenWidth:   640,
    62  		screenHeight:  480,
    63  		hint:          true,
    64  		hintRenderer:  hintRenderer,
    65  	}
    66  	game.refreshScreenProperties()
    67  	return game
    68  }
    69  
    70  func (self *Game) refreshScreenProperties() {
    71  	size := self.txtRenderer.GetSizePxFract()
    72  	spacing := int((float64(size) * GlyphSpacing) / 64)
    73  	sizer := self.txtRenderer.GetSizer().(*esizer.FixedSizer)
    74  	sizer.SetAdvance(spacing)
    75  	glyphsPerLine := self.screenWidth / spacing
    76  	if glyphsPerLine != len(self.glyphs) {
    77  		self.glyphs = make([]etxt.GlyphIndex, glyphsPerLine)
    78  	}
    79  	self.spacing = spacing
    80  	self.bottomFade = ebiten.NewImage(self.screenWidth, 16)
    81  	self.bottomFade.Fill(color.RGBA{0, 0, 0, 64})
    82  }
    83  
    84  func (self *Game) Layout(w int, h int) (int, int) {
    85  	if w != self.screenWidth || h != self.screenHeight {
    86  		self.screenWidth = w
    87  		self.screenHeight = h
    88  		self.refreshScreenProperties()
    89  	}
    90  	return w, h
    91  }
    92  
    93  func (self *Game) Update() error {
    94  	now := time.Now()
    95  	if now.Sub(self.lastPressTime) < time.Millisecond*250 {
    96  		return nil
    97  	}
    98  
    99  	if ebiten.IsKeyPressed(ebiten.KeyF) {
   100  		ebiten.SetFullscreen(!ebiten.IsFullscreen())
   101  		self.lastPressTime = now
   102  		return nil
   103  	}
   104  
   105  	if ebiten.IsKeyPressed(ebiten.KeyH) {
   106  		self.hint = !self.hint
   107  		self.lastPressTime = now
   108  		return nil
   109  	}
   110  
   111  	up := ebiten.IsKeyPressed(ebiten.KeyUp)
   112  	down := ebiten.IsKeyPressed(ebiten.KeyDown)
   113  	glyphsPerLine := len(self.glyphs)
   114  	line := self.position / glyphsPerLine
   115  	maxLine := (self.numGlyphs + glyphsPerLine - 1) / glyphsPerLine
   116  	maxLine -= (self.screenHeight / self.spacing)
   117  	if self.numGlyphs > glyphsPerLine {
   118  		maxLine += 1
   119  	}
   120  	if maxLine < 0 {
   121  		maxLine = 0
   122  	}
   123  
   124  	if self.position > 0 && up {
   125  		self.position -= glyphsPerLine
   126  		if self.position < 0 {
   127  			self.position = 0
   128  		}
   129  		self.lastPressTime = now
   130  	} else if line < maxLine && down {
   131  		self.position += glyphsPerLine
   132  		if self.position >= self.numGlyphs {
   133  			self.position = self.numGlyphs - 1
   134  		}
   135  		self.lastPressTime = now
   136  	}
   137  	return nil
   138  }
   139  
   140  func (self *Game) Draw(screen *ebiten.Image) {
   141  	// dark background
   142  	screen.Fill(color.RGBA{0, 0, 0, 255})
   143  
   144  	// draw text
   145  	self.txtRenderer.SetTarget(screen)
   146  	currentPos := self.position
   147  	sp := self.spacing // I'm gonna use this to death
   148  	lineWidth := sp*len(self.glyphs) - sp/3
   149  	leftMargin := (self.screenWidth - lineWidth) / 2
   150  	for i := 0; i < (self.screenHeight+sp-1)/sp; i++ {
   151  		// set the glyph indices to draw
   152  		lastPos := currentPos + len(self.glyphs)
   153  		if lastPos >= self.numGlyphs {
   154  			lastPos = self.numGlyphs
   155  		}
   156  		if lastPos <= currentPos {
   157  			continue
   158  		}
   159  		for i := 0; currentPos+i < lastPos; i++ {
   160  			self.glyphs[i] = etxt.GlyphIndex(currentPos + i)
   161  		}
   162  
   163  		// draw the glyphs
   164  		glyphs := self.glyphs[0 : lastPos-currentPos]
   165  		x, y := leftMargin, (sp*3)/4+i*sp
   166  		self.txtRenderer.TraverseGlyphs(glyphs, fixed.P(x, y),
   167  			func(dot fixed.Point26_6, index etxt.GlyphIndex) {
   168  				mask := self.txtRenderer.LoadGlyphMask(index, dot)
   169  				self.txtRenderer.DefaultDrawFunc(dot, mask, index)
   170  			})
   171  
   172  		// advance to next glyphs
   173  		currentPos = lastPos
   174  	}
   175  
   176  	// make the bottom fade out
   177  	for i := 0; i < 16; i++ {
   178  		opts := &ebiten.DrawImageOptions{}
   179  		opts.GeoM.Translate(0, float64(self.screenHeight-16+i))
   180  		screen.DrawImage(self.bottomFade, opts)
   181  	}
   182  
   183  	// draw current glyph index and total
   184  	if self.hint && self.hintRenderer != nil {
   185  		self.hintRenderer.SetTarget(screen)
   186  		text := fmt.Sprintf("%d of %d glyphs", self.position+1, self.numGlyphs)
   187  		self.hintRenderer.Draw(text, self.screenWidth-4, self.screenHeight-6)
   188  		self.hintRenderer.SetTarget(nil)
   189  	}
   190  }
   191  
   192  func main() {
   193  	// get font path
   194  	if len(os.Args) != 2 {
   195  		msg := "Usage: expects one argument with the path to the font to be used\n"
   196  		fmt.Fprint(os.Stderr, msg)
   197  		os.Exit(1)
   198  	}
   199  
   200  	// parse font
   201  	font, fontName, err := etxt.ParseFontFrom(os.Args[1])
   202  	if err != nil {
   203  		log.Fatal(err)
   204  	}
   205  	fmt.Printf("Font loaded: %s\n", fontName)
   206  
   207  	// create cache
   208  	cache := etxt.NewDefaultCache(10 * 1024 * 1024) // 10MB
   209  	// **IMPORTANT CACHE INFO**
   210  	// In almost every example we have been setting caches to 1GB,
   211  	// mostly because we weren't expecting to fill them anywhere near
   212  	// that. In this example, instead, we are setting the cache to a
   213  	// more reasonable value, because otherwise the cache could really
   214  	// fill up a lot for some fonts.
   215  	//
   216  	// This example is almost a textbook situation for a cache: glyphs
   217  	// only appear once, and if they are on the screen they will be
   218  	// heavily reused, but once we scroll past them, they aren't likely
   219  	// to come up again. And... if they come up again, it is because we
   220  	// saw them recently and scrolled back to them. So least recently
   221  	// used policies are a perfect fit for this. Technically, the default
   222  	// cache uses random sampling for eviction, not perfect LRU, but
   223  	// the results should be quite decent anyway.
   224  
   225  	// create and configure renderer
   226  	renderer := etxt.NewStdRenderer()
   227  	renderer.SetCacheHandler(cache.NewHandler())
   228  	renderer.SetSizePx(GlyphSize)
   229  	renderer.SetFont(font)
   230  	renderer.SetAlign(etxt.YCenter, etxt.Left)
   231  
   232  	// determine if we have the right glyphs to show hint text
   233  	const alphabet = " ofglyphs0123456789"
   234  	missing, err := etxt.GetMissingRunes(font, alphabet)
   235  	if err != nil {
   236  		log.Fatal(err)
   237  	}
   238  
   239  	// run the game
   240  	ebiten.SetWindowTitle("etxt/examples/ebiten/all_glyphs")
   241  	ebiten.SetWindowResizable(true)
   242  	ebiten.SetWindowSize(640, 480)
   243  	ebiten.SetCursorMode(ebiten.CursorModeHidden) // doing this right, boys...
   244  	err = ebiten.RunGame(NewGame(renderer, font, len(missing) > 0))
   245  	if err != nil {
   246  		log.Fatal(err)
   247  	}
   248  }