1 // -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
2 // (c) 2020 Henner Zeller <h.zeller@acm.org>
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation version 2.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://gnu.org/licenses/gpl-2.0.txt>
15
16 #include "renderer.h"
17
18 #include <string.h>
19 #include <unistd.h>
20
21 namespace timg {
TrimTitle(const char * title,int requested_width)22 std::string Renderer::TrimTitle(const char *title, int requested_width) {
23 std::string result = title;
24 // Columns can be too narrow. We might need to trim what we print.
25 if ((int)result.length() > requested_width) {
26 result.replace(0, result.length() - requested_width + 3, "...");
27 } else if (options_.center_horizontally) {
28 const int start_spaces = (requested_width - result.length()) / 2;
29 result.insert(0, std::string(start_spaces, ' '));
30 }
31 result += "\n";
32 return result;
33 }
34
35 namespace {
36 // Use the full canvas to display framebuffer; writes framebuffer directly.
37 class SingleColumnRenderer final : public Renderer {
38 public:
SingleColumnRenderer(timg::TerminalCanvas * canvas,const DisplayOptions & display_opts)39 SingleColumnRenderer(timg::TerminalCanvas *canvas,
40 const DisplayOptions &display_opts)
41 : Renderer(canvas, display_opts) {}
42
render_cb(const char * title)43 WriteFramebufferFun render_cb(const char *title) final {
44 // For single column mode, implementation is straightforward
45 RenderTitle(title);
46 return [this](int x, int dy, const Framebuffer &fb,
47 SeqType seq_type, Duration end_of_frame) {
48 canvas_->Send(x, dy, fb, seq_type, end_of_frame);
49 };
50 }
51
52 private:
RenderTitle(const char * title)53 void RenderTitle(const char *title) {
54 if (!title || !options_.show_filename) return;
55 const std::string tout = TrimTitle(
56 title, options_.width / options_.cell_x_px);
57 canvas_->AddPrefixNextSend(tout.data(), tout.size());
58 }
59 };
60
61 // The multi column renderer positions every update in a new column.
62 // It keeps track which column it is in and if a new row needs to be started
63 // and uses cursor movements to get to the right place.
64 class MultiColumnRenderer final : public Renderer {
65 public:
MultiColumnRenderer(timg::TerminalCanvas * canvas,const DisplayOptions & display_opts,int cols,int rows)66 MultiColumnRenderer(timg::TerminalCanvas *canvas,
67 const DisplayOptions &display_opts,
68 int cols, int rows)
69 : Renderer(canvas, display_opts),
70 columns_(cols), column_width_(display_opts.width) {
71 }
72
~MultiColumnRenderer()73 ~MultiColumnRenderer() final {
74 if (current_column_ != 0) {
75 const int down = highest_fb_column_height_ - last_fb_height_;
76 if (down > 0) {
77 canvas_->MoveCursorDY(down / options_.cell_y_px);
78 }
79 }
80 }
81
render_cb(const char * title)82 WriteFramebufferFun render_cb(const char *title) final {
83 ++current_column_;
84 if (current_column_ >= columns_) {
85 // If our current image is shorter than the previous one,
86 // we need to make up the difference to be ready for the next
87 const int down = highest_fb_column_height_ - last_fb_height_;
88 if (down > 0) canvas_->MoveCursorDY(down);
89 current_column_ = 0;
90 highest_fb_column_height_ = 0;
91 }
92
93 PrepareTitle(title);
94 first_render_call_ = true;
95 return [this](int x, int dy, const Framebuffer &fb,
96 SeqType seq_type, Duration end_of_frame) {
97 const int x_offset = current_column_ * column_width_;
98 int y_offset;
99 if (first_render_call_) {
100 // Unless we're in the first column, we've to move up from last
101 y_offset = current_column_ > 0 ? -last_fb_height_ : 0;
102 } else {
103 y_offset = dy;
104 }
105 if (options_.show_filename && first_render_call_) {
106 // If we have to show the headline, we've to do the move up
107 // before Send() would do it.
108 // We need to move one more up to be in the 'headline' ypos
109 if (y_offset) {
110 // Move pixels needed for cell, then one more
111 // then one line more to reach the place of the title.
112 const int y_move = (-y_offset + options_.cell_y_px - 1) /
113 options_.cell_y_px;
114 const int kSpaceForTitle = 1;
115 canvas_->MoveCursorDY(-y_move - kSpaceForTitle);
116 }
117 canvas_->MoveCursorDX(x_offset / options_.cell_x_px);
118 canvas_->AddPrefixNextSend(title_.c_str(), title_.length());
119 y_offset = 0; // No move by Send() needed anymore.
120 }
121
122 canvas_->Send(x + x_offset, y_offset, fb, seq_type, end_of_frame);
123 last_fb_height_ = fb.height();
124 if (last_fb_height_ > highest_fb_column_height_)
125 highest_fb_column_height_ = last_fb_height_;
126 first_render_call_ = false;
127 };
128 }
129
130 private:
PrepareTitle(const char * title)131 void PrepareTitle(const char *title) {
132 if (!title || !options_.show_filename) return;
133 title_ = TrimTitle(title, column_width_ / options_.cell_x_px);
134 }
135
136 const int columns_;
137 const int column_width_;
138 std::string title_;
139 bool first_render_call_ = true;
140 int current_column_ = -1;
141 int highest_fb_column_height_ = 0; // maximum seen in this row
142 int last_fb_height_ = 0;
143 };
144
145 } // namespace
146
Renderer(timg::TerminalCanvas * canvas,const DisplayOptions & display_opts)147 Renderer::Renderer(timg::TerminalCanvas *canvas,
148 const DisplayOptions &display_opts)
149 : canvas_(canvas), options_(display_opts) {}
150
Create(timg::TerminalCanvas * output,const DisplayOptions & display_opts,int cols,int rows)151 std::unique_ptr<Renderer> Renderer::Create(timg::TerminalCanvas *output,
152 const DisplayOptions &display_opts,
153 int cols, int rows) {
154 if (cols > 1) {
155 return std::make_unique<MultiColumnRenderer>(output, display_opts,
156 cols, rows);
157 }
158 return std::make_unique<SingleColumnRenderer>(output, display_opts);
159 }
160
161 }
162