Start working on a simple Launcher app.

Let GButton have an optional icon (GraphicsBitmap) that gets rendered in the
middle of the button if present.

Also add GraphicsBitmap::load_from_file() which allows mmap'ed RGBA32 files.
I wrote a little program to take "raw" files from GIMP and swizzle them into
the correct byte order.
This commit is contained in:
Andreas Kling 2019-02-07 23:13:47 +01:00
parent 71b9ec1ae0
commit 887b4a7a1a
Notes: sideshowbarker 2024-07-19 15:50:11 +09:00
29 changed files with 293 additions and 11 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

BIN
Base/res/icons/Terminal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

BIN
Base/res/icons/Terminal.rgb Normal file

Binary file not shown.

BIN
Base/res/icons/file.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

BIN
Base/res/icons/file.rgb Normal file

Binary file not shown.

BIN
Base/res/icons/folder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
Base/res/icons/folder.rgb Normal file

Binary file not shown.

BIN
Base/res/icons/generic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
Base/res/icons/generic.rgb Normal file

Binary file not shown.

View file

@ -26,7 +26,8 @@
#include "BochsVGADevice.h" #include "BochsVGADevice.h"
//#define SPAWN_GUITEST //#define SPAWN_GUITEST
#define SPAWN_GUITEST2 #define SPAWN_LAUNCHER
//#define SPAWN_GUITEST2
#define SPAWN_CLOCK #define SPAWN_CLOCK
//#define SPAWN_FONTEDITOR //#define SPAWN_FONTEDITOR
//#define SPAWN_MULTIPLE_SHELLS //#define SPAWN_MULTIPLE_SHELLS
@ -113,6 +114,9 @@ static void init_stage2()
#ifdef SPAWN_GUITEST2 #ifdef SPAWN_GUITEST2
Process::create_user_process("/bin/guitest2", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0); Process::create_user_process("/bin/guitest2", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0);
#endif #endif
#ifdef SPAWN_LAUNCHER
Process::create_user_process("/bin/Launcher", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0);
#endif
#ifdef SPAWN_CLOCK #ifdef SPAWN_CLOCK
Process::create_user_process("/bin/Clock", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0); Process::create_user_process("/bin/Clock", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0);
#endif #endif

View file

@ -14,6 +14,8 @@ make -C ../FontEditor clean && \
make -C ../FontEditor && \ make -C ../FontEditor && \
make -C ../Clock clean && \ make -C ../Clock clean && \
make -C ../Clock && \ make -C ../Clock && \
make -C ../Launcher clean && \
make -C ../Launcher && \
make clean &&\ make clean &&\
make && \ make && \
sudo ./sync.sh sudo ./sync.sh

View file

@ -10,6 +10,8 @@ make -C ../Terminal clean && \
make -C ../Terminal && \ make -C ../Terminal && \
make -C ../Clock clean && \ make -C ../Clock clean && \
make -C ../Clock && \ make -C ../Clock && \
make -C ../Launcher clean && \
make -C ../Launcher && \
make -C ../Userland clean && \ make -C ../Userland clean && \
make -C ../Userland && \ make -C ../Userland && \
sudo ./sync.sh sudo ./sync.sh

View file

@ -47,6 +47,7 @@ cp -v ../Userland/guitest2 mnt/bin/guitest2
cp -v ../Userland/sysctl mnt/bin/sysctl cp -v ../Userland/sysctl mnt/bin/sysctl
cp -v ../Terminal/Terminal mnt/bin/Terminal cp -v ../Terminal/Terminal mnt/bin/Terminal
cp -v ../FontEditor/FontEditor mnt/bin/FontEditor cp -v ../FontEditor/FontEditor mnt/bin/FontEditor
cp -v ../Launcher/Launcher mnt/bin/Launcher
cp -v ../Clock/Clock mnt/bin/Clock cp -v ../Clock/Clock mnt/bin/Clock
ln -s FontEditor mnt/bin/ff ln -s FontEditor mnt/bin/ff
ln -s Clock mnt/bin/cl ln -s Clock mnt/bin/cl

3
Launcher/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.o
*.d
FontEditor

BIN
Launcher/Launcher Executable file

Binary file not shown.

34
Launcher/Makefile Normal file
View file

@ -0,0 +1,34 @@
OBJS = \
main.o
APP = Launcher
ARCH_FLAGS =
STANDARD_FLAGS = -std=c++17 -nostdinc++ -nostdlib -nostdinc
USERLAND_FLAGS = -ffreestanding -fno-stack-protector -fno-ident
WARNING_FLAGS = -Wextra -Wall -Wundef -Wcast-qual -Wwrite-strings
FLAVOR_FLAGS = -march=i386 -mregparm=3 -m32 -fno-exceptions -fno-rtti -fmerge-all-constants -fno-unroll-loops -fno-pie -fno-pic
OPTIMIZATION_FLAGS = -Oz -fno-asynchronous-unwind-tables
INCLUDE_FLAGS = -I.. -I. -I../LibC
DEFINES = -DSERENITY -DSANITIZE_PTRS -DUSERLAND
CXXFLAGS = -MMD -MP $(WARNING_FLAGS) $(OPTIMIZATION_FLAGS) $(USERLAND_FLAGS) $(FLAVOR_FLAGS) $(ARCH_FLAGS) $(STANDARD_FLAGS) $(INCLUDE_FLAGS) $(DEFINES)
CXX = clang
LD = ld
AR = ar
LDFLAGS = -static --strip-debug -melf_i386 -e _start --gc-sections
all: $(APP)
$(APP): $(OBJS)
$(LD) -o $(APP) $(LDFLAGS) $(OBJS) ../LibGUI/LibGUI.a ../LibC/LibC.a
.cpp.o:
@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
-include $(OBJS:%.o=%.d)
clean:
@echo "CLEAN"; rm -f $(APPS) $(OBJS) *.d

79
Launcher/main.cpp Normal file
View file

@ -0,0 +1,79 @@
#include <SharedGraphics/GraphicsBitmap.h>
#include <LibGUI/GWindow.h>
#include <LibGUI/GWidget.h>
#include <LibGUI/GButton.h>
#include <LibGUI/GEventLoop.h>
#include <signal.h>
#include <unistd.h>
static GWindow* make_launcher_window();
void handle_sigchld(int)
{
dbgprintf("Launcher(%d) Got SIGCHLD\n", getpid());
int pid = waitpid(-1, nullptr, 0);
dbgprintf("Launcher(%d) waitpid() returned %d\n", getpid(), pid);
ASSERT(pid > 0);
}
int main(int, char**)
{
signal(SIGCHLD, handle_sigchld);
GEventLoop loop;
auto* launcher_window = make_launcher_window();
launcher_window->set_should_exit_app_on_close(true);
launcher_window->show();
return loop.exec();
}
GWindow* make_launcher_window()
{
auto* window = new GWindow;
window->set_title("Launcher");
window->set_rect({ 50, 50, 300, 60 });
auto* widget = new GWidget;
window->set_main_widget(widget);
widget->set_relative_rect({ 0, 0, 300, 60 });
auto* terminal_button = new GButton(widget);
terminal_button->set_relative_rect({ 5, 5, 50, 50 });
terminal_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/Terminal.rgb", { 32, 32 }));
terminal_button->on_click = [] (GButton&) {
pid_t child_pid = fork();
if (!child_pid) {
execve("/bin/Terminal", nullptr, nullptr);
ASSERT_NOT_REACHED();
}
};
auto* font_editor_button = new GButton(widget);
font_editor_button->set_relative_rect({ 60, 5, 50, 50 });
font_editor_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/FontEditor.rgb", { 32, 32 }));
font_editor_button->on_click = [] (GButton&) {
pid_t child_pid = fork();
if (!child_pid) {
execve("/bin/FontEditor", nullptr, nullptr);
ASSERT_NOT_REACHED();
}
};
auto* guitest_editor_button = new GButton(widget);
guitest_editor_button->set_relative_rect({ 115, 5, 50, 50 });
guitest_editor_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/generic.rgb", { 32, 32 }));
guitest_editor_button->on_click = [] (GButton&) {
pid_t child_pid = fork();
if (!child_pid) {
execve("/bin/guitest", nullptr, nullptr);
ASSERT_NOT_REACHED();
}
};
return window;
}

View file

@ -55,11 +55,19 @@ void GButton::paint_event(GPaintEvent&)
painter.draw_line({ 2, height() - 3 }, { width() - 2, height() - 3 }, shadow_color); painter.draw_line({ 2, height() - 3 }, { width() - 2, height() - 3 }, shadow_color);
} }
if (!caption().is_empty()) { if (!caption().is_empty() || m_icon) {
auto text_rect = rect(); auto content_rect = rect();
if (m_being_pressed) auto icon_location = m_icon ? content_rect.center().translated(-(m_icon->width() / 2), -(m_icon->height() / 2)) : Point();
text_rect.move_by(1, 1); if (m_being_pressed) {
painter.draw_text(text_rect, caption(), Painter::TextAlignment::Center, Color::Black); content_rect.move_by(1, 1);
icon_location.move_by(1, 1);
}
if (m_icon) {
painter.blit_with_alpha(icon_location, *m_icon, m_icon->rect());
painter.draw_text(content_rect, caption(), Painter::TextAlignment::Center, Color::Black);
} else {
painter.draw_text(content_rect, caption(), Painter::TextAlignment::Center, Color::Black);
}
} }
} }

View file

@ -3,6 +3,7 @@
#include "GWidget.h" #include "GWidget.h"
#include <AK/AKString.h> #include <AK/AKString.h>
#include <AK/Function.h> #include <AK/Function.h>
#include <SharedGraphics/GraphicsBitmap.h>
class GButton final : public GWidget { class GButton final : public GWidget {
public: public:
@ -12,6 +13,10 @@ public:
String caption() const { return m_caption; } String caption() const { return m_caption; }
void set_caption(String&&); void set_caption(String&&);
void set_icon(RetainPtr<GraphicsBitmap>&& icon) { m_icon = move(icon); }
const GraphicsBitmap* icon() const { return m_icon.ptr(); }
GraphicsBitmap* icon() { return m_icon.ptr(); }
Function<void(GButton&)> on_click; Function<void(GButton&)> on_click;
private: private:
@ -23,6 +28,7 @@ private:
virtual const char* class_name() const override { return "GButton"; } virtual const char* class_name() const override { return "GButton"; }
String m_caption; String m_caption;
RetainPtr<GraphicsBitmap> m_icon;
bool m_being_pressed { false }; bool m_being_pressed { false };
bool m_tracking_cursor { false }; bool m_tracking_cursor { false };
}; };

View file

@ -29,9 +29,20 @@ public:
Color(byte r, byte g, byte b) : m_value((r << 16) | (g << 8) | b) { } Color(byte r, byte g, byte b) : m_value((r << 16) | (g << 8) | b) { }
Color(RGBA32 rgba) : m_value(rgba) { } Color(RGBA32 rgba) : m_value(rgba) { }
int red() const { return (m_value >> 16) & 0xff; } byte red() const { return (m_value >> 16) & 0xff; }
int green() const { return (m_value >> 8) & 0xff; } byte green() const { return (m_value >> 8) & 0xff; }
int blue() const { return m_value & 0xff; } byte blue() const { return m_value & 0xff; }
byte alpha() const { return (m_value >> 24) & 0xff; }
Color blend(Color source) const
{
RGBA32 redblue1 = ((0x100u - source.alpha()) * (m_value & 0xff00ff)) >> 8;
RGBA32 redblue2 = (source.alpha() * (source.m_value & 0xff00ff)) >> 8;
RGBA32 green1 = ((0x100u - source.alpha()) * (m_value & 0x00ff00)) >> 8;
RGBA32 green2 = (source.alpha() * (source.m_value & 0x00ff00)) >> 8;
return Color(((redblue1 | redblue2) & 0xff00ff) + ((green1 | green2) & 0x00ff00));
}
RGBA32 value() const { return m_value; } RGBA32 value() const { return m_value; }

View file

@ -1,5 +1,4 @@
#include "GraphicsBitmap.h" #include "GraphicsBitmap.h"
#include <AK/kmalloc.h>
#ifdef KERNEL #ifdef KERNEL
#include <Kernel/Process.h> #include <Kernel/Process.h>
@ -7,6 +6,14 @@
#include <WindowServer/WSMessageLoop.h> #include <WindowServer/WSMessageLoop.h>
#endif #endif
#ifdef USERLAND
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#endif
#ifdef KERNEL #ifdef KERNEL
RetainPtr<GraphicsBitmap> GraphicsBitmap::create(Process& process, const Size& size) RetainPtr<GraphicsBitmap> GraphicsBitmap::create(Process& process, const Size& size)
{ {
@ -39,6 +46,49 @@ RetainPtr<GraphicsBitmap> GraphicsBitmap::create_wrapper(const Size& size, RGBA3
return adopt(*new GraphicsBitmap(size, data)); return adopt(*new GraphicsBitmap(size, data));
} }
RetainPtr<GraphicsBitmap> GraphicsBitmap::load_from_file(const String& path, const Size& size)
{
#ifdef USERLAND
int fd = open(path.characters(), O_RDONLY, 0644);
if (fd < 0) {
dbgprintf("open(%s) got fd=%d, failed: %s\n", path.characters(), fd, strerror(errno));
perror("open");
return nullptr;
}
auto* mapped_file = (RGBA32*)mmap(nullptr, size.area() * 4, PROT_READ, MAP_SHARED, fd, 0);
if (mapped_file == MAP_FAILED) {
int rc = close(fd);
ASSERT(rc == 0);
return nullptr;
}
#else
int error;
auto descriptor = VFS::the().open(path, error, 0, 0, *VFS::the().root_inode());
if (!descriptor) {
kprintf("Failed to load GraphicsBitmap from file (%s)\n", path.characters());
ASSERT_NOT_REACHED();
}
auto* region = current->allocate_file_backed_region(LinearAddress(), size.area() * 4, descriptor->inode(), ".rgb file", /*readable*/true, /*writable*/false);
region->page_in();
auto* mapped_file = (RGBA32*)region->laddr().get();
#endif
#ifdef USERLAND
int rc = close(fd);
ASSERT(rc == 0);
#endif
auto bitmap = create_wrapper(size, mapped_file);
#ifdef KERNEL
bitmap->m_server_region = region;
#else
bitmap->m_mmaped = true;
#endif
return bitmap;
}
GraphicsBitmap::GraphicsBitmap(const Size& size, RGBA32* data) GraphicsBitmap::GraphicsBitmap(const Size& size, RGBA32* data)
: m_size(size) : m_size(size)
, m_data(data) , m_data(data)
@ -53,6 +103,11 @@ GraphicsBitmap::~GraphicsBitmap()
m_client_process->deallocate_region(*m_client_region); m_client_process->deallocate_region(*m_client_region);
if (m_server_region) if (m_server_region)
WSMessageLoop::the().server_process().deallocate_region(*m_server_region); WSMessageLoop::the().server_process().deallocate_region(*m_server_region);
#else
if (m_mmaped) {
int rc = munmap(m_data, m_size.area() * 4);
ASSERT(rc == 0);
}
#endif #endif
m_data = nullptr; m_data = nullptr;
} }

View file

@ -5,6 +5,7 @@
#include "Size.h" #include "Size.h"
#include <AK/Retainable.h> #include <AK/Retainable.h>
#include <AK/RetainPtr.h> #include <AK/RetainPtr.h>
#include <AK/AKString.h>
#ifdef KERNEL #ifdef KERNEL
#include "Process.h" #include "Process.h"
@ -16,6 +17,7 @@ public:
static RetainPtr<GraphicsBitmap> create(Process&, const Size&); static RetainPtr<GraphicsBitmap> create(Process&, const Size&);
#endif #endif
static RetainPtr<GraphicsBitmap> create_wrapper(const Size&, RGBA32*); static RetainPtr<GraphicsBitmap> create_wrapper(const Size&, RGBA32*);
static RetainPtr<GraphicsBitmap> load_from_file(const String& path, const Size&);
~GraphicsBitmap(); ~GraphicsBitmap();
RGBA32* scanline(int y); RGBA32* scanline(int y);
@ -42,6 +44,10 @@ private:
RGBA32* m_data { nullptr }; RGBA32* m_data { nullptr };
size_t m_pitch { 0 }; size_t m_pitch { 0 };
#ifdef USERLAND
bool m_mmaped { false };
#endif
#ifdef KERNEL #ifdef KERNEL
WeakPtr<Process> m_client_process; WeakPtr<Process> m_client_process;
Region* m_client_region { nullptr }; Region* m_client_region { nullptr };

View file

@ -368,6 +368,7 @@ void Painter::draw_focus_rect(const Rect& rect)
void Painter::blit(const Point& position, const GraphicsBitmap& source, const Rect& src_rect) void Painter::blit(const Point& position, const GraphicsBitmap& source, const Rect& src_rect)
{ {
Rect dst_rect(position, src_rect.size()); Rect dst_rect(position, src_rect.size());
dst_rect.move_by(m_translation);
dst_rect.intersect(m_clip_rect); dst_rect.intersect(m_clip_rect);
RGBA32* dst = m_target->scanline(dst_rect.y()) + dst_rect.x(); RGBA32* dst = m_target->scanline(dst_rect.y()) + dst_rect.x();
@ -383,6 +384,33 @@ void Painter::blit(const Point& position, const GraphicsBitmap& source, const Re
} }
} }
void Painter::blit_with_alpha(const Point& position, const GraphicsBitmap& source, const Rect& src_rect)
{
Rect dst_rect(position, src_rect.size());
dst_rect.move_by(m_translation);
dst_rect.intersect(m_clip_rect);
RGBA32* dst = m_target->scanline(dst_rect.y()) + dst_rect.x();
const RGBA32* src = source.scanline(src_rect.top()) + src_rect.left();
const unsigned dst_skip = m_target->width();
const unsigned src_skip = source.width();
for (int i = dst_rect.height() - 1; i >= 0; --i) {
for (int x = 0; x < dst_rect.width(); ++x) {
byte alpha = Color(src[x]).alpha();
if (alpha == 0xff)
dst[x] = src[x];
else if (!alpha)
continue;
else
dst[x] = Color(dst[x]).blend(src[x]).value();
}
dst += dst_skip;
src += src_skip;
}
}
void Painter::set_clip_rect(const Rect& rect) void Painter::set_clip_rect(const Rect& rect)
{ {
m_clip_rect = Rect::intersection(rect, m_target->rect()); m_clip_rect = Rect::intersection(rect, m_target->rect());

View file

@ -32,6 +32,7 @@ public:
void draw_line(const Point&, const Point&, Color); void draw_line(const Point&, const Point&, Color);
void draw_focus_rect(const Rect&); void draw_focus_rect(const Rect&);
void blit(const Point&, const GraphicsBitmap&, const Rect& src_rect); void blit(const Point&, const GraphicsBitmap&, const Rect& src_rect);
void blit_with_alpha(const Point&, const GraphicsBitmap&, const Rect& src_rect);
enum class TextAlignment { TopLeft, CenterLeft, Center, CenterRight }; enum class TextAlignment { TopLeft, CenterLeft, Center, CenterRight };
void draw_text(const Rect&, const String&, TextAlignment = TextAlignment::TopLeft, Color = Color()); void draw_text(const Rect&, const String&, TextAlignment = TextAlignment::TopLeft, Color = Color());

View file

@ -26,6 +26,13 @@ public:
move_by(delta.x(), delta.y()); move_by(delta.x(), delta.y());
} }
Point translated(int dx, int dy)
{
Point point = *this;
point.move_by(dx, dy);
return point;
}
void constrain(const Rect&); void constrain(const Rect&);
bool operator==(const Point& other) const bool operator==(const Point& other) const

View file

@ -13,6 +13,8 @@ public:
int width() const { return m_width; } int width() const { return m_width; }
int height() const { return m_height; } int height() const { return m_height; }
int area() const { return width() * height(); }
void set_width(int w) { m_width = w; } void set_width(int w) { m_width = w; }
void set_height(int h) { m_height = h; } void set_height(int h) { m_height = h; }

View file

@ -78,7 +78,7 @@ GWindow* make_font_test_window()
GWindow* make_launcher_window() GWindow* make_launcher_window()
{ {
auto* window = new GWindow; auto* window = new GWindow;
window->set_title("Launcher"); window->set_title("guitest2");
window->set_rect({ 100, 400, 100, 230 }); window->set_rect({ 100, 400, 100, 230 });
auto* widget = new GWidget; auto* widget = new GWidget;

View file

@ -0,0 +1,33 @@
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char**argv)
{
int fd = open(argv[1], O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
for (;;) {
unsigned buffer;
ssize_t nread = read(fd, &buffer, sizeof(buffer));
if (nread == 0)
break;
if (nread < 0) {
perror("read");
return 1;
}
unsigned converted = buffer & 0xff00ff00;
converted |= (buffer & 0xff0000) >> 16;
converted |= (buffer & 0xff) << 16;
write(1, &converted, sizeof(unsigned));
}
int rc = close(fd);
if (rc < 0) {
perror("close");
return 1;
}
return 0;
}