1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include "OSystem.hxx"
19#include "Console.hxx"
20#include "Debugger.hxx"
21#include "DebuggerParser.hxx"
22#include "TIA.hxx"
23#include "FrameBuffer.hxx"
24#include "FBSurface.hxx"
25#include "Widget.hxx"
26#include "GuiObject.hxx"
27#include "ContextMenu.hxx"
28#include <math.h>
29
30#include "TiaZoomWidget.hxx"
31
32// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
33TiaZoomWidget::TiaZoomWidget(GuiObject* boss, const GUI::Font& font,
34 int x, int y, int w, int h)
35 : Widget(boss, font, x, y, 16, 16),
36 CommandSender(boss)
37{
38 _flags = Widget::FLAG_ENABLED | Widget::FLAG_CLEARBG |
39 Widget::FLAG_RETAIN_FOCUS | Widget::FLAG_TRACK_MOUSE;
40 _bgcolor = _bgcolorhi = kDlgColor;
41
42 // Use all available space, up to the maximum bounds of the TIA image
43 _w = std::min(w, 320);
44 _h = std::min(h, 260);
45
46 addFocusWidget(this);
47
48 // Initialize positions
49 myZoomLevel = 2;
50 myNumCols = (_w - 4) / myZoomLevel;
51 myNumRows = (_h - 4) / myZoomLevel;
52 myOffX = myOffY = 0;
53
54 myMouseMoving = false;
55 myClickX = myClickY = 0;
56
57 // Create context menu for zoom levels
58 VariantList l;
59 VarList::push_back(l, "Fill to scanline", "scanline");
60 VarList::push_back(l, "Toggle breakpoint", "bp");
61 VarList::push_back(l, "2x zoom", "2");
62 VarList::push_back(l, "4x zoom", "4");
63 VarList::push_back(l, "8x zoom", "8");
64 myMenu = make_unique<ContextMenu>(this, font, l);
65}
66
67// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
68void TiaZoomWidget::loadConfig()
69{
70 setDirty();
71}
72
73// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
74void TiaZoomWidget::setPos(int x, int y)
75{
76 // Center on given x,y point
77 myOffX = x - (myNumCols >> 1);
78 myOffY = y - (myNumRows >> 1);
79
80 recalc();
81}
82
83// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
84void TiaZoomWidget::zoom(int level)
85{
86 if(myZoomLevel == level)
87 return;
88
89 // zoom towards mouse position
90 myOffX = round(myOffX + myClickX / myZoomLevel - myClickX / level);
91 myOffY = round(myOffY + myClickY / myZoomLevel - myClickY / level);
92
93 myZoomLevel = level;
94 myNumCols = (_w - 4) / myZoomLevel & 0xfffe; // must be even!
95 myNumRows = (_h - 4) / myZoomLevel;
96
97 recalc();
98}
99
100// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
101void TiaZoomWidget::recalc()
102{
103 const int tw = instance().console().tia().width(),
104 th = instance().console().tia().height();
105
106 // Don't go past end of framebuffer
107 myOffX = BSPF::clamp(myOffX, 0, (tw << 1) - myNumCols);
108 myOffY = BSPF::clamp(myOffY, 0, th - myNumRows);
109
110 setDirty();
111}
112
113// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
114void TiaZoomWidget::handleMouseDown(int x, int y, MouseButton b, int clickCount)
115{
116 myClickX = x;
117 myClickY = y;
118
119 // Button 1 is for 'drag'/movement of the image
120 // Button 2 is for context menu
121 if(b == MouseButton::LEFT)
122 {
123 // Indicate mouse drag started/in progress
124 myMouseMoving = true;
125 myOffXLo = myOffYLo = 0;
126 }
127 else if(b == MouseButton::RIGHT)
128 {
129 // Add menu at current x,y mouse location
130 myMenu->show(x + getAbsX(), y + getAbsY(), dialog().surface().dstRect());
131 }
132}
133
134// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
135void TiaZoomWidget::handleMouseUp(int x, int y, MouseButton b, int clickCount)
136{
137 myMouseMoving = false;
138}
139
140// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
141void TiaZoomWidget::handleMouseWheel(int x, int y, int direction)
142{
143 // zoom towards mouse position
144 myClickX = x;
145 myClickY = y;
146
147 if(direction > 0)
148 {
149 if (myZoomLevel > 1)
150 zoom(myZoomLevel - 1);
151 }
152 else
153 {
154 if (myZoomLevel < 8)
155 zoom(myZoomLevel + 1);
156 }
157}
158
159// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
160void TiaZoomWidget::handleMouseMoved(int x, int y)
161{
162 if(myMouseMoving)
163 {
164 int diffx = x + myOffXLo - myClickX;
165 int diffy = y + myOffYLo - myClickY;
166
167 myClickX = x;
168 myClickY = y;
169
170 myOffX -= diffx / myZoomLevel;
171 myOffY -= diffy / myZoomLevel;
172 // handle remainder
173 myOffXLo = diffx % myZoomLevel;
174 myOffYLo = diffy % myZoomLevel;
175
176 recalc();
177 }
178}
179
180// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
181void TiaZoomWidget::handleMouseEntered()
182{
183 setFlags(Widget::FLAG_HILITED);
184 setDirty();
185}
186
187// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
188void TiaZoomWidget::handleMouseLeft()
189{
190 clearFlags(Widget::FLAG_HILITED);
191 setDirty();
192 myMouseMoving = false;
193}
194
195// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
196bool TiaZoomWidget::handleEvent(Event::Type event)
197{
198 bool handled = true;
199
200 switch(event)
201 {
202 case Event::UIUp:
203 myOffY -= 4;
204 break;
205
206 case Event::UIDown:
207 myOffY += 4;
208 break;
209
210 case Event::UILeft:
211 myOffX -= 4;
212 break;
213
214 case Event::UIRight:
215 myOffX += 4;
216 break;
217
218 case Event::UIPgUp:
219 myOffY = 0;
220 break;
221
222 case Event::UIPgDown:
223 myOffY = _h;
224 break;
225
226 case Event::UIHome:
227 myOffX = 0;
228 break;
229
230 case Event::UIEnd:
231 myOffX = _w;
232 break;
233
234 default:
235 handled = false;
236 break;
237 }
238
239 if(handled)
240 recalc();
241
242 return handled;
243}
244
245// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
246void TiaZoomWidget::handleCommand(CommandSender* sender, int cmd, int data, int id)
247{
248 switch(cmd)
249 {
250 case ContextMenu::kItemSelectedCmd:
251 {
252 uInt32 ystart = instance().console().tia().ystart();
253 const string& rmb = myMenu->getSelectedTag().toString();
254
255 if(rmb == "scanline")
256 {
257 ostringstream command;
258 int lines = myClickY / myZoomLevel + myOffY + ystart - instance().console().tia().scanlines();
259
260 if (lines < 0)
261 lines += instance().console().tia().scanlinesLastFrame();
262 if(lines > 0)
263 {
264 command << "scanline #" << lines;
265 string message = instance().debugger().parser().run(command.str());
266 instance().frameBuffer().showMessage(message);
267 }
268 }
269 else if(rmb == "bp")
270 {
271 ostringstream command;
272 int scanline = myClickY / myZoomLevel + myOffY + ystart;
273 command << "breakif _scan==#" << scanline;
274 string message = instance().debugger().parser().run(command.str());
275 instance().frameBuffer().showMessage(message);
276 }
277 else
278 {
279 int level = myMenu->getSelectedTag().toInt();
280 if(level > 0)
281 zoom(level);
282 }
283 break;
284 }
285 }
286}
287
288// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
289void TiaZoomWidget::drawWidget(bool hilite)
290{
291//cerr << "TiaZoomWidget::drawWidget\n";
292 FBSurface& s = dialog().surface();
293
294 s.fillRect(_x+1, _y+1, _w-2, _h-2, kBGColor);
295 s.frameRect(_x, _y, _w, _h, hilite ? kWidColorHi : kColor);
296
297 // Draw the zoomed image
298 // This probably isn't as efficient as it can be, but it's a small area
299 // and I don't have time to make it faster :)
300 const uInt8* currentFrame = instance().console().tia().outputBuffer();
301 const int width = instance().console().tia().width(),
302 wzoom = myZoomLevel << 1,
303 hzoom = myZoomLevel;
304
305 // Get current scanline position
306 // This determines where the frame greying should start
307 uInt32 scanx, scany, scanoffset;
308 instance().console().tia().electronBeamPos(scanx, scany);
309 scanoffset = width * scany + scanx;
310
311 int x, y, col, row;
312 for(y = myOffY, row = 0; y < myNumRows+myOffY; ++y, row += hzoom)
313 {
314 for(x = myOffX >> 1, col = 0; x < (myNumCols+myOffX) >> 1; ++x, col += wzoom)
315 {
316 uInt32 idx = y*width + x;
317 ColorId color = ColorId(currentFrame[idx] | (idx > scanoffset ? 1 : 0));
318 s.fillRect(_x + col + 1, _y + row + 1, wzoom, hzoom, color);
319 }
320 }
321}
322