1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 11:48:10 +00:00
serenity/Userland/Demos/Fire/Fire.cpp
Andreas Kling 41dc73adc4 Demos: Fix a bunch of incorrect use of GUI::PaintEvent::rect()
A bunch of programs were using the paint event rect as the rect
to draw into. Since the event rect could be any invalidated part
of the widget, we need to be passing the full Widget::rect().
2021-05-09 10:11:30 +02:00

240 lines
6.7 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
/* Fire.cpp - a (classic) graphics demo for Serenity, by pd.
* heavily based on the Fabien Sanglard's article:
* http://fabiensanglard.net/doom_fire_psx/index.html
*
* Future directions:
* [X] This does suggest the need for a palletized graphics surface. Thanks kling!
* [X] alternate column updates, or vertical interlacing. this would certainly alter
* the effect, but the update load would be halved.
* [/] scaled blit
* [ ] dithering?
* [X] inlining rand()
* [/] precalculating and recycling random data
* [ ] rework/expand palette
* [ ] switch to use tsc values for perf check
* [ ] handle mouse events differently for smoother painting (queue)
* [ ] handle fire bitmap edges better
*/
#include <LibCore/ElapsedTimer.h>
#include <LibGUI/Action.h>
#include <LibGUI/Application.h>
#include <LibGUI/Icon.h>
#include <LibGUI/Label.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Menubar.h>
#include <LibGUI/Painter.h>
#include <LibGUI/Widget.h>
#include <LibGUI/Window.h>
#include <LibGfx/Bitmap.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#define FIRE_WIDTH 320
#define FIRE_HEIGHT 168
#define FIRE_MAX 29
static const Color s_palette[] = {
Color(0x07, 0x07, 0x07), Color(0x1F, 0x07, 0x07), Color(0x2F, 0x0F, 0x07),
Color(0x47, 0x0F, 0x07), Color(0x57, 0x17, 0x07), Color(0x67, 0x1F, 0x07),
Color(0x77, 0x1F, 0x07), Color(0x9F, 0x2F, 0x07), Color(0xAF, 0x3F, 0x07),
Color(0xBF, 0x47, 0x07), Color(0xC7, 0x47, 0x07), Color(0xDF, 0x4F, 0x07),
Color(0xDF, 0x57, 0x07), Color(0xD7, 0x5F, 0x07), Color(0xD7, 0x5F, 0x07),
Color(0xD7, 0x67, 0x0F), Color(0xCF, 0x6F, 0x0F), Color(0xCF, 0x7F, 0x0F),
Color(0xCF, 0x87, 0x17), Color(0xC7, 0x87, 0x17), Color(0xC7, 0x8F, 0x17),
Color(0xC7, 0x97, 0x1F), Color(0xBF, 0x9F, 0x1F), Color(0xBF, 0xA7, 0x27),
Color(0xBF, 0xAF, 0x2F), Color(0xB7, 0xAF, 0x2F), Color(0xB7, 0xB7, 0x37),
Color(0xCF, 0xCF, 0x6F), Color(0xEF, 0xEF, 0xC7), Color(0xFF, 0xFF, 0xFF)
};
class Fire : public GUI::Widget {
C_OBJECT(Fire)
public:
virtual ~Fire() override;
void set_stat_label(RefPtr<GUI::Label> l) { stats = l; };
private:
Fire();
RefPtr<Gfx::Bitmap> bitmap;
RefPtr<GUI::Label> stats;
virtual void paint_event(GUI::PaintEvent&) override;
virtual void timer_event(Core::TimerEvent&) override;
virtual void mousedown_event(GUI::MouseEvent& event) override;
virtual void mousemove_event(GUI::MouseEvent& event) override;
virtual void mouseup_event(GUI::MouseEvent& event) override;
bool dragging;
int timeAvg;
int cycles;
int phase;
};
Fire::Fire()
{
bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::Indexed8, { 320, 200 });
/* Initialize fire palette */
for (int i = 0; i < 30; i++)
bitmap->set_palette_color(i, s_palette[i]);
/* Set remaining entries to white */
for (int i = 30; i < 256; i++)
bitmap->set_palette_color(i, Color::White);
dragging = false;
timeAvg = 0;
cycles = 0;
phase = 0;
srand(time(nullptr));
stop_timer();
start_timer(20);
/* Draw fire "source" on bottom row of pixels */
for (int i = 0; i < FIRE_WIDTH; i++)
bitmap->scanline_u8(bitmap->height() - 1)[i] = FIRE_MAX;
/* Set off initital paint event */
//update();
}
Fire::~Fire()
{
}
void Fire::paint_event(GUI::PaintEvent& event)
{
Core::ElapsedTimer timer;
timer.start();
GUI::Painter painter(*this);
painter.add_clip_rect(event.rect());
painter.draw_scaled_bitmap(rect(), *bitmap, bitmap->rect());
timeAvg += timer.elapsed();
cycles++;
}
void Fire::timer_event(Core::TimerEvent&)
{
/* Update only even or odd columns per frame... */
phase++;
if (phase > 1)
phase = 0;
/* Paint our palettized buffer to screen */
for (int px = 0 + phase; px < FIRE_WIDTH; px += 2) {
for (int py = 1; py < 200; py++) {
int rnd = rand() % 3;
/* Calculate new pixel value, don't go below 0 */
u8 nv = bitmap->scanline_u8(py)[px];
if (nv > 0)
nv -= (rnd & 1);
/* ...sigh... */
int epx = px + (1 - rnd);
if (epx < 0)
epx = 0;
else if (epx > FIRE_WIDTH)
epx = FIRE_WIDTH;
bitmap->scanline_u8(py - 1)[epx] = nv;
}
}
if ((cycles % 50) == 0) {
dbgln("{} total cycles. finished 50 in {} ms, avg {} ms", cycles, timeAvg, timeAvg / 50);
stats->set_text(String::formatted("{} ms", timeAvg / 50));
timeAvg = 0;
}
update();
}
void Fire::mousedown_event(GUI::MouseEvent& event)
{
if (event.button() == GUI::MouseButton::Left)
dragging = true;
return GUI::Widget::mousedown_event(event);
}
/* FIXME: needs to account for the size of the window rect */
void Fire::mousemove_event(GUI::MouseEvent& event)
{
if (dragging) {
if (event.y() >= 2 && event.y() < 398 && event.x() <= 638) {
int ypos = event.y() / 2;
int xpos = event.x() / 2;
bitmap->scanline_u8(ypos - 1)[xpos] = FIRE_MAX + 5;
bitmap->scanline_u8(ypos - 1)[xpos + 1] = FIRE_MAX + 5;
bitmap->scanline_u8(ypos)[xpos] = FIRE_MAX + 5;
bitmap->scanline_u8(ypos)[xpos + 1] = FIRE_MAX + 5;
}
}
return GUI::Widget::mousemove_event(event);
}
void Fire::mouseup_event(GUI::MouseEvent& event)
{
if (event.button() == GUI::MouseButton::Left)
dragging = false;
return GUI::Widget::mouseup_event(event);
}
int main(int argc, char** argv)
{
auto app = GUI::Application::construct(argc, argv);
if (pledge("stdio recvfd sendfd rpath", nullptr) < 0) {
perror("pledge");
return 1;
}
if (unveil("/res", "r") < 0) {
perror("unveil");
return 1;
}
if (unveil(nullptr, nullptr) < 0) {
perror("unveil");
return 1;
}
auto window = GUI::Window::construct();
window->set_double_buffering_enabled(false);
window->set_title("Fire");
window->set_resizable(false);
window->resize(640, 400);
auto menubar = GUI::Menubar::construct();
auto& app_menu = menubar->add_menu("File");
app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); }));
window->set_menubar(move(menubar));
auto& fire = window->set_main_widget<Fire>();
auto& time = fire.add<GUI::Label>();
time.set_relative_rect({ 0, 4, 40, 10 });
time.move_by({ window->width() - time.width(), 0 });
fire.set_stat_label(time);
window->show();
auto app_icon = GUI::Icon::default_icon("app-fire");
window->set_icon(app_icon.bitmap_for_size(16));
return app->exec();
}