From 05cda98c854332a094f0e24f65ded8f4034edf28 Mon Sep 17 00:00:00 2001 From: Brian Fiete Date: Sat, 1 Feb 2025 10:39:04 -0800 Subject: [PATCH] BMP loading, show workspace icon files on startup panel --- BeefySysLib/BeefySysLib.vcxproj | 2 + BeefySysLib/BeefySysLib.vcxproj.filters | 6 + BeefySysLib/BeefySysLib_static.vcxproj | 2 + .../BeefySysLib_static.vcxproj.filters | 6 + BeefySysLib/gfx/RenderDevice.cpp | 7 + BeefySysLib/gfx/RenderDevice.h | 1 + BeefySysLib/img/BMPData.cpp | 531 ++++++++++++++++++ BeefySysLib/img/BMPData.h | 41 ++ BeefySysLib/platform/win/DXRenderDevice.cpp | 3 + IDE/src/ui/StartupPanel.bf | 72 ++- IDE/src/util/ResourceGen.bf | 58 ++ 11 files changed, 725 insertions(+), 4 deletions(-) create mode 100644 BeefySysLib/img/BMPData.cpp create mode 100644 BeefySysLib/img/BMPData.h diff --git a/BeefySysLib/BeefySysLib.vcxproj b/BeefySysLib/BeefySysLib.vcxproj index 03d89b6a..24f095c5 100644 --- a/BeefySysLib/BeefySysLib.vcxproj +++ b/BeefySysLib/BeefySysLib.vcxproj @@ -463,6 +463,7 @@ copy /y "$(OutDir)$(TargetName).lib" "$(SolutionDir)\BeefLibs\Beefy2D\dist\" + @@ -1985,6 +1986,7 @@ copy /y "$(OutDir)$(TargetName).lib" "$(SolutionDir)\BeefLibs\Beefy2D\dist\" + diff --git a/BeefySysLib/BeefySysLib.vcxproj.filters b/BeefySysLib/BeefySysLib.vcxproj.filters index 91db0e80..55154045 100644 --- a/BeefySysLib/BeefySysLib.vcxproj.filters +++ b/BeefySysLib/BeefySysLib.vcxproj.filters @@ -740,6 +740,9 @@ src\util + + src\img + @@ -1141,6 +1144,9 @@ src\util + + src\img + diff --git a/BeefySysLib/BeefySysLib_static.vcxproj b/BeefySysLib/BeefySysLib_static.vcxproj index 9f5b1345..ac170baa 100644 --- a/BeefySysLib/BeefySysLib_static.vcxproj +++ b/BeefySysLib/BeefySysLib_static.vcxproj @@ -199,6 +199,7 @@ + @@ -908,6 +909,7 @@ + diff --git a/BeefySysLib/BeefySysLib_static.vcxproj.filters b/BeefySysLib/BeefySysLib_static.vcxproj.filters index 9af9f333..9bc062c7 100644 --- a/BeefySysLib/BeefySysLib_static.vcxproj.filters +++ b/BeefySysLib/BeefySysLib_static.vcxproj.filters @@ -590,6 +590,9 @@ src\third_party\putty + + src\img + @@ -907,6 +910,9 @@ src\third_party\putty + + src\img + diff --git a/BeefySysLib/gfx/RenderDevice.cpp b/BeefySysLib/gfx/RenderDevice.cpp index 80bac63b..3eededbf 100644 --- a/BeefySysLib/gfx/RenderDevice.cpp +++ b/BeefySysLib/gfx/RenderDevice.cpp @@ -6,6 +6,7 @@ #include "img/TGAData.h" #include "img/PNGData.h" #include "img/PVRData.h" +#include "img/BMPData.h" #include "img/BFIData.h" #include "img/JPEGData.h" #include "util/PerfTimer.h" @@ -142,6 +143,12 @@ Texture* RenderDevice::LoadTexture(const StringImpl& fileName, int flags) imageData = new JPEGData(); else if (ext == ".pvr") imageData = new PVRData(); + else if (ext == ".bmp") + { + BMPData* bmpData = new BMPData(); + bmpData->mHasTransFollowing = (flags & TextureFlag_HasTransFollowing) == 0;; + imageData = bmpData; + } else { return NULL; // Unknown format diff --git a/BeefySysLib/gfx/RenderDevice.h b/BeefySysLib/gfx/RenderDevice.h index c189fbdb..96a05c54 100644 --- a/BeefySysLib/gfx/RenderDevice.h +++ b/BeefySysLib/gfx/RenderDevice.h @@ -156,6 +156,7 @@ enum TextureFlag : int8 TextureFlag_Additive = 1, TextureFlag_NoPremult = 2, TextureFlag_AllowRead = 4, + TextureFlag_HasTransFollowing = 8 }; struct VertexDefData diff --git a/BeefySysLib/img/BMPData.cpp b/BeefySysLib/img/BMPData.cpp new file mode 100644 index 00000000..1019e9fc --- /dev/null +++ b/BeefySysLib/img/BMPData.cpp @@ -0,0 +1,531 @@ +#include "BMPData.h" +#include +#include +#include +#include + +USING_NS_BF; + +#pragma warning(disable:4996) + +int BMPData::Read(void* ptr, int elemSize, int elemCount) +{ + int maxReadCount = (mSrcDataLen - mReadPos) / elemSize; + if (elemCount > maxReadCount) + elemCount = maxReadCount; + memcpy(ptr, mSrcData + mReadPos, elemCount * elemSize); + mReadPos += elemCount * elemSize; + return elemCount; +} + +unsigned char BMPData::ReadC() +{ + if (mReadPos >= mSrcDataLen) + return 0; + return mSrcData[mReadPos++]; +} + +BMPData::BMPData() +{ + mHasTransFollowing = false; + mReadPos = 0; +} + +#define BITMAP_MAGIC_NUMBER 19778 + +typedef struct bmp_file_header_s bmp_file_header_t; +struct bmp_file_header_s +{ + /* int16_t magic_number; */ /* because of padding, we don't put it into the struct */ + int32_t size; + int32_t app_id; + int32_t offset; +}; + +typedef struct bmp_bitmap_info_header_s bmp_bitmap_info_header_t; +struct bmp_bitmap_info_header_s +{ + int32_t header_size; + int32_t width; + int32_t height; + int16_t num_planes; + int16_t bpp; + int32_t compression; + int32_t image_size; + int32_t horizontal_resolution; + int32_t vertical_resolution; + int32_t colors_used; + int32_t colors_important; +}; + +//typedef struct bmp_palette_element_s bmp_palette_element_t; + + +bool BMPData::ReadPixelsRLE8(bmp_palette_element_t* palette) +{ + unsigned char byte, index, i, * p; + unsigned char x_ofs, y_ofs; + char keepreading = 1; + int current_line = 0; + unsigned char* dest = (unsigned char*)mBits; + + p = dest; + while (keepreading) { /* in each loop, we read a pair of bytes */ + byte = ReadC(); /* read the first byte */ + if (byte) { + index = ReadC(); /* read the second byte */ + for (i = 0; i < byte; i++) { + *p = palette[index].red; p++; /* unindex pixels on the fly */ + *p = palette[index].green; p++; + *p = palette[index].blue; p++; + *p = 0xFF; p++; /* add alpha */ + } + } + else { + byte = ReadC(); /* read the second byte */ + switch (byte) + { + case 0: /* skip the end of the current line and go to the next line */ + current_line++; + p = dest + current_line * mWidth * 4; + break; + case 1: /* stop reading */ + keepreading = 0; + break; + case 2: /* skip y_ofs lines and x_ofs columns. This means that the ignored pixels will be + filled with black (Or maybe i didn't understand the spec ?). This has already be done : + before starting to load pixel data, we filled all the dest[] array with 0x00 by a memset() */ + x_ofs = ReadC(); + y_ofs = ReadC(); + current_line += y_ofs; + p += y_ofs * mWidth * 4 + x_ofs * 4; + break; + default: /* get the next n pixels, where n = byte */ + for (i = 0; i < byte; i++) { + index = ReadC(); + *p = palette[index].red; p++; /* unindex pixels on the fly */ + *p = palette[index].green; p++; + *p = palette[index].blue; p++; + *p = 0xFF; p++; /* add alpha */ + } + /* if n is not a multiple of 2, then skip one byte in the file, to respect int16_t alignment */ + if (byte % 2) mReadPos++; + break; + } + } + /* the place to which p is pointing is guided by the content of the file. A corrupted file could make p point to a wrong localization. + Hence, we must check that we don't point outside of the dest[] array. This may prevent an error of segmentation */ + if (p >= dest + mWidth * mHeight * 4) keepreading = 0; + } + + return true; +} + +bool BMPData::ReadPixelsRLE4(bmp_palette_element_t* palette) +{ + unsigned char byte1, byte2, index1, index2, * p; + unsigned char x_ofs, y_ofs; + unsigned char bitmask = 0x0F; /* bit mask : 00001111 */ + char keepreading = 1; + int current_line = 0; + int i; + + unsigned char* dest = (unsigned char*)mBits; + + p = dest; + while (keepreading) { + byte1 = ReadC(); + if (byte1) { /* encoded mode */ + byte2 = ReadC(); + index1 = byte2 >> 4; /* get the first 4 bits of byte2 */ + index2 = byte2 & bitmask; /* get the next 4 bits of byte2 */ + for (i = 0; i < (byte1 / 2); i++) { + *p = palette[index1].red; p++; + *p = palette[index1].green; p++; + *p = palette[index1].blue; p++; + *p = 0x0F; p++; + *p = palette[index2].red; p++; + *p = palette[index2].green; p++; + *p = palette[index2].blue; p++; + *p = 0x0F; p++; + } + if (byte1 % 2) { + *p = palette[index1].red; p++; + *p = palette[index1].green; p++; + *p = palette[index1].blue; p++; + *p = 0x0F; p++; + } + } + else { /* absolute mode */ + byte2 = ReadC(); + switch (byte2) + { + case 0: /* skip the end of the current line and go to the next line */ + current_line++; + p = dest + current_line * mWidth * 4; + break; + case 1: /* stop reading */ + keepreading = 0; + break; + case 2: /* skip y_ofs lines and x_ofs column */ + x_ofs = ReadC(); + y_ofs = ReadC(); + current_line += y_ofs; + p += y_ofs * mWidth * 4 + x_ofs * 4; + break; + default: /* get the next n pixels, where n = byte2 */ + for (i = 0; i < (byte2 / 2); i++) { + byte1 = ReadC(); + index1 = byte1 >> 4; + *p = palette[index1].red; p++; + *p = palette[index1].green; p++; + *p = palette[index1].blue; p++; + *p = 0x0F; p++; + index2 = byte1 & bitmask; + *p = palette[index2].red; p++; + *p = palette[index2].green; p++; + *p = palette[index2].blue; p++; + *p = 0x0F; p++; + } + if (byte2 % 2) { + byte1 = ReadC(); + index1 = byte1 >> 4; + *p = palette[index1].red; p++; + *p = palette[index1].green; p++; + *p = palette[index1].blue; p++; + *p = 0x0F; p++; + } + if (((byte2 + 1) / 2) % 2) /* int16_t alignment */ + mReadPos += 1; + break; + } + } + /* the place to which p is pointing is guided by the content of the file. A corrupted file could make p point to a wrong localization. + Hence, we must check that we don't point outside of the dest[] array. This may prevent an error of segmentation */ + if (p >= dest + mWidth * mHeight * 4) keepreading = 0; + } + + return true; +} + +bool BMPData::ReadPixels32() +{ + int i, j; + unsigned char px[4], * p; + + unsigned char* dest = (unsigned char*)mBits; + + for (i = 0; i < mHeight; i++) + { + p = dest + (mHeight - i - 1) * mWidth * 4; + for (j = 0; j < mWidth; j++) + { + Read(px, 4, 1); /* convert BGRX to RGBA */ + *p = px[2]; p++; // R + *p = px[1]; p++; + *p = px[0]; p++; + *p = px[3]; p++; + } + } + + return true; +} + +bool BMPData::ReadPixels24() +{ + int i, j; + unsigned char px[3], * p; + + unsigned char* dest = (unsigned char*)mBits; + + for (i = 0; i < mHeight; i++) + { + p = dest + (mHeight - i - 1) * mWidth * 4; + for (j = 0; j < mWidth; j++) + { + Read(px, 3, 1); + *p = px[2]; p++; /* convert BGR to RGBA */ + *p = px[1]; p++; + *p = px[0]; p++; + *p = 0xFF; p++; /* add alpha component */ + } + if (mWidth * 3 % 4 != 0) + mReadPos += 4 - (mWidth * 3 % 4); /* if the width is not a multiple of 4, skip the end of the line */ + } + + return true; +} + +/* Expected format : XBGR 0 11111 11111 11111 */ +bool BMPData::ReadPixels16() +{ + uint16_t pxl, r, g, b; + uint16_t bitmask = 0x1F; + unsigned char* p; + int i, j; + + unsigned char* dest = (unsigned char*)mBits; + + p = dest; + for (i = 0; i < mHeight; i++) + { + for (j = 0; j < mWidth; j++) + { + Read(&pxl, 2, 1); + b = (pxl >> 10) & bitmask; + g = (pxl >> 5) & bitmask; + r = pxl & bitmask; + *p = r * 8; p++; /* fix me */ + *p = g * 8; p++; + *p = b * 8; p++; + *p = 0xFF; p++; + } + if ((2 * mWidth) % 4 != 0) + mReadPos += 4 - ((2 * mWidth) % 4); + } + + return true; +} + +bool BMPData::ReadPixels8(bmp_palette_element_t* palette) +{ + int i, j; + unsigned char px, * p; + + unsigned char* dest = (unsigned char*)mBits; + + p = dest; + for (i = 0; i < mHeight; i++) { + for (j = 0; j < mWidth; j++) { + Read(&px, 1, 1); + *p = palette[px].red; p++; + *p = palette[px].green; p++; + *p = palette[px].blue; p++; + *p = 0xFF; p++; + } + if (mWidth % 4 != 0) + mReadPos += 4 - (mWidth % 4); + } + + return true; +} + +bool BMPData::ReadPixels4(bmp_palette_element_t* palette) +{ + int size = (mWidth + 1) / 2; /* byte alignment */ + unsigned char* row_stride = new unsigned char[size]; /* not C90 but convenient here */ + defer({ delete row_stride; }); + + unsigned char index, byte, * p; + unsigned char bitmask = 0x0F; /* bit mask : 00001111 */ + + unsigned char* dest = (unsigned char*)mBits; + + p = dest; + for (int i = 0; i < mHeight; i++) + { + Read(row_stride, size, 1); + for (int j = 0; j < mWidth; j++) + { + byte = row_stride[j / 2]; + index = (j % 2) ? bitmask & byte : byte >> 4; + *p = palette[index].red; p++; + *p = palette[index].green; p++; + *p = palette[index].blue; p++; + *p = 0xFF; p++; + } + if (size % 4 != 0) + mReadPos += 4 - (size % 4); + } + + return true; +} + +bool BMPData::ReadPixels1(bmp_palette_element_t* palette) +{ + int size = (mWidth + 7) / 8; /* byte alignment */ + unsigned char* row_stride = new unsigned char[size]; /* not C90 but convenient here */ + defer({ delete row_stride; }); + + unsigned char index, byte, * p; + unsigned char bitmask = 0x01; /* bit mask : 00000001 */ + int bit; + + unsigned char* dest = (unsigned char*)mBits; + + p = dest; + for (int i = 0; i < mHeight; i++) { + Read(row_stride, size, 1); + for (int j = 0; j < mWidth; j++) { + bit = (j % 8) + 1; + byte = row_stride[j / 8]; + index = byte >> (8 - bit); + index &= bitmask; + *p = palette[index].red; p++; + *p = palette[index].green; p++; + *p = palette[index].blue; p++; + *p = 0xFF; p++; + } + if (size % 4 != 0) + mReadPos += 4 - (size % 4); + } + + return true; +} + +bool BMPData::ReadData() +{ + int16_t magic_number; + bmp_file_header_t file_header; + bmp_bitmap_info_header_t info_header; + bmp_palette_element_t* palette = NULL; + + Read(&magic_number, 2, 1); + if (magic_number == BITMAP_MAGIC_NUMBER) + { + Read((void*)&file_header, 12, 1); + Read((void*)&info_header, 40, 1); + mReadPos = file_header.offset; + } + else + { + mReadPos = 0; + Read((void*)&info_header, 40, 1); + } + + /* info_header sanity checks */ + /* accepted headers : bitmapinfoheader, bitmapv4header, bitmapv5header */ + if (!(info_header.header_size == 40 || info_header.header_size == 108 || info_header.header_size == 124)) { + return false; + } + if (info_header.num_planes != 1) { + return false; + } + if (info_header.compression == 4 || info_header.compression == 5) { + return false; + } + if (info_header.height < 0) { + return false; + } + + /* load palette, if present */ + if (info_header.bpp <= 8) { + mReadPos = 14 + info_header.header_size; + if ((info_header.bpp == 1) && (info_header.colors_used == 0)) info_header.colors_used = 2; + if ((info_header.bpp == 4) && (info_header.colors_used == 0)) info_header.colors_used = 16; + if ((info_header.bpp == 8) && (info_header.colors_used == 0)) info_header.colors_used = 256; + palette = (bmp_palette_element_t*)malloc(info_header.colors_used * sizeof(bmp_palette_element_t)); + if (!palette) { + return false; + } + else + Read((void*)palette, sizeof(bmp_palette_element_t), info_header.colors_used); + } + + /* memory allocation */ + //buf = malloc(info_header.width * info_header.height * 4); + mWidth = info_header.width; + mHeight = info_header.height; + mBits = new uint32[mWidth * mHeight]; + + memset(mBits, 0x00, info_header.width * info_header.height * 4); + + /* load image data */ + switch (info_header.bpp) + { + case 32: + ReadPixels32(); + break; + case 24: + ReadPixels24(); + break; + case 16: + ReadPixels16(); + break; + case 8: + if (info_header.compression == 1) + ReadPixelsRLE8(palette); + else + ReadPixels8(palette); + break; + case 4: + if (info_header.compression == 2) + ReadPixelsRLE4(palette); + else + ReadPixels4(palette); + break; + case 1: + ReadPixels1(palette); + break; + } + + if (info_header.colors_used) free(palette); + + if (mHasTransFollowing) + { + mHeight /= 2; + auto newBits = new uint32[mWidth * mHeight]; + memcpy(newBits, mBits + mHeight * mWidth, mWidth * mHeight * 4); + delete mBits; + mBits = newBits; + } + + return true; +} + +bool BMPData::WriteToFile(const StringImpl& path) +{ + FILE* file; + int16_t magic_number; + bmp_file_header_t file_header; + bmp_bitmap_info_header_t info_header; + unsigned char sample[3], * p; + int i, j; + + file = fopen(path.c_str(), "wb"); + if (!file) + { + return false; + } + + magic_number = BITMAP_MAGIC_NUMBER; + + file_header.size = mWidth * mHeight * 4 + 54; + file_header.app_id = 0; + file_header.offset = 54; + + info_header.header_size = 40; + info_header.width = mWidth; + info_header.height = mHeight; + info_header.num_planes = 1; + info_header.bpp = 24; + info_header.compression = 0; + info_header.image_size = mWidth * mHeight * 4; + info_header.horizontal_resolution = 0; + info_header.vertical_resolution = 0; + info_header.colors_used = 0; + info_header.colors_important = 0; + + fwrite(&magic_number, sizeof(magic_number), 1, file); + fwrite(&file_header, sizeof(bmp_file_header_t), 1, file); + fwrite(&info_header, sizeof(bmp_bitmap_info_header_t), 1, file); + + p = (unsigned char*)mBits; + for (i = 0; i < mHeight; i++) + { + for (j = 0; j < mWidth; j++) + { + /* convert RGBA to BGR */ + sample[2] = *p; p++; + sample[1] = *p; p++; + sample[0] = *p; p++; + p++; + + fwrite(sample, 3, 1, file); + } + } + + fclose(file); + + return 0; +} diff --git a/BeefySysLib/img/BMPData.h b/BeefySysLib/img/BMPData.h new file mode 100644 index 00000000..23341652 --- /dev/null +++ b/BeefySysLib/img/BMPData.h @@ -0,0 +1,41 @@ +#pragma once + +#include "ImageData.h" + +NS_BF_BEGIN; + +struct bmp_palette_element_s +{ + unsigned char blue; + unsigned char green; + unsigned char red; + unsigned char reserved; /* alpha ? */ +}; +typedef struct bmp_palette_element_s bmp_palette_element_t; + +class BMPData : public ImageData +{ +public: + int mReadPos; + bool mHasTransFollowing; + + int Read(void* ptr, int elemSize, int elemCount); + unsigned char ReadC(); + + bool ReadPixelsRLE8(bmp_palette_element_t* palette); + bool ReadPixelsRLE4(bmp_palette_element_t* palette); + bool ReadPixels32(); + bool ReadPixels24(); + bool ReadPixels16(); + bool ReadPixels8(bmp_palette_element_t* palette); + bool ReadPixels4(bmp_palette_element_t* palette); + bool ReadPixels1(bmp_palette_element_t* palette); + +public: + BMPData(); + + bool ReadData(); + bool WriteToFile(const StringImpl& path); +}; + +NS_BF_END; diff --git a/BeefySysLib/platform/win/DXRenderDevice.cpp b/BeefySysLib/platform/win/DXRenderDevice.cpp index d4cb4d2e..729d0453 100644 --- a/BeefySysLib/platform/win/DXRenderDevice.cpp +++ b/BeefySysLib/platform/win/DXRenderDevice.cpp @@ -692,6 +692,9 @@ void DXTexture::SetBits(int destX, int destY, int destWidth, int destHeight, int void DXTexture::GetBits(int srcX, int srcY, int srcWidth, int srcHeight, int destPitch, uint32* bits) { + if ((srcWidth <= 0) || (srcHeight <= 0)) + return; + D3D11_TEXTURE2D_DESC texDesc; texDesc.ArraySize = 1; texDesc.BindFlags = 0; diff --git a/IDE/src/ui/StartupPanel.bf b/IDE/src/ui/StartupPanel.bf index adfbfbb2..f11c17d6 100644 --- a/IDE/src/ui/StartupPanel.bf +++ b/IDE/src/ui/StartupPanel.bf @@ -1,3 +1,5 @@ +#pragma warning disable 168 + using System; using System.IO; using System.Collections; @@ -9,6 +11,7 @@ using IDE.util; using Beefy.events; using Beefy.theme; using System.Diagnostics; +using Beefy.utils; namespace IDE.ui { @@ -17,6 +20,7 @@ namespace IDE.ui class RecentWorkspacesScrollWidget : ScrollableWidget { public Font mTitleFont; + public bool mHasIcons; public this() { @@ -31,7 +35,7 @@ namespace IDE.ui public override void Resize(float x, float y, float width, float height) { - const float MARGIN = 3; + const float MARGIN = 0; float currentY = 0; float fillWidth = width - (mVertScrollbar?.Width).GetValueOrDefault(); @@ -40,7 +44,7 @@ namespace IDE.ui { for (let widget in mScrollContent.mChildWidgets) { - widget.Resize(0, currentY, fillWidth, GS!(30)); + widget.Resize(0, currentY, fillWidth, GS!(33)); currentY += widget.Height + MARGIN; } } @@ -60,7 +64,7 @@ namespace IDE.ui g.SetFont(mTitleFont); using (g.PushColor(gApp.mSettings.mUISettings.mColors.mText)) - g.DrawString("Recent Workspaces", 0, GS!(-30), .Centered, mWidth, .Ellipsis); + g.DrawString("Recent Workspaces", GS!(2), GS!(-30), .Centered, mWidth - GS!(4), .Ellipsis); base.Draw(g); } @@ -70,6 +74,8 @@ namespace IDE.ui { public static Font s_Font; append String mPath; + public bool mTriedLoadIcon; + public Image mIcon ~ delete _; public bool mPinned; public RecentWorkspacesScrollWidget mRecentsParent; @@ -91,9 +97,67 @@ namespace IDE.ui } + if (!mTriedLoadIcon) + { + mTriedLoadIcon = true; + + StructuredData sd = scope .(); + if (sd.Load(scope $"{mPath}/BeefProj.toml") case .Ok) + { + using (sd.Open("Platform")) + { + using (sd.Open("Windows")) + { + var iconFileName = sd.GetString("IconFile", .. scope .()); + iconFileName.Replace("$(ProjectDir)", mPath); + iconFileName.Replace("$(WorkspaceDir)", mPath); + var iconFilePath = IO.Path.GetAbsolutePath(iconFileName, mPath, .. scope .()); + + if (File.Exists(iconFilePath)) + { + int wantSize = 32; + if (var image = ResourceGen.LoadIcon(iconFilePath, wantSize)) + { + if ((image.mWidth != wantSize) || (image.mHeight != wantSize)) + { + image.Scale(wantSize / Math.Max(image.mWidth, image.mHeight)); + } + mIcon = image; + mRecentsParent.mHasIcons = true; + } + } + } + } + } + } + g.SetFont(s_Font); using (g.PushColor(gApp.mSettings.mUISettings.mColors.mText)) - g.DrawString(mPath, 10, 0, .Left, mWidth - 10); + { + String drawStr = scope String(); + int lastSlash = Math.Max(mPath.LastIndexOf('\\'), mPath.LastIndexOf('/')); + if (lastSlash != -1) + { + drawStr.Append(Font.EncodeColor(0x80FFFFFF)); + drawStr.Append(mPath.Substring(0, lastSlash + 1)); + drawStr.Append(Font.EncodePopColor()); + drawStr.Append(mPath.Substring(lastSlash + 1)); + } + else + { + drawStr.Append(mPath); + } + + float drawX = GS!(50); + g.DrawString(drawStr, drawX, GS!(3), .Left, mWidth - drawX - GS!(2), .Ellipsis); + } + if (mIcon != null) + g.DrawCentered(mIcon, GS!(24), mHeight / 2); + else + { + using (g.PushColor(0x80FFFFFF)) + g.DrawCentered(DarkTheme.sDarkTheme.GetImage(.Workspace), GS!(24), mHeight / 2); + } } public override void MouseEnter() diff --git a/IDE/src/util/ResourceGen.bf b/IDE/src/util/ResourceGen.bf index 88dc98be..dcc59fe7 100644 --- a/IDE/src/util/ResourceGen.bf +++ b/IDE/src/util/ResourceGen.bf @@ -1,8 +1,12 @@ +#pragma warning disable 168 + using System; using System.IO; using System.Collections; using System.Text; using System.Diagnostics; +using Beefy.gfx; + namespace IDE.util { class ResourceGen @@ -62,6 +66,60 @@ namespace IDE.util FileStream mOutStream = new FileStream() ~ delete _; + public static Result LoadIcon(String iconFile, int wantSize = -1) + { + FileStream stream = scope FileStream(); + if (stream.Open(iconFile, .Read) case .Err) + { + return .Err; + } + + let iconDir = Try!(stream.Read()); + if ((iconDir.mReserved != 0) || (iconDir.mType != 1) || (iconDir.mCount > 0x100)) + { + return .Err; + } + + var entries = scope List(); + + for (int idx < iconDir.mCount) + { + entries.Add(Try!(stream.Read())); + } + + int bestIdx = iconDir.mCount - 1; + for (int idx < iconDir.mCount) + { + let iconEntry = ref entries[idx]; + if (iconEntry.mWidth >= wantSize) + { + bestIdx = idx; + break; + } + } + + let iconEntry = ref entries[bestIdx]; + + Try!(stream.Seek(iconEntry.mImageOffset)); + + if (iconEntry.mBytesInRes > 1024*1024) + { + return .Err; + } + + uint8* data = new:ScopedAlloc! uint8[iconEntry.mBytesInRes]*; + + Try!(stream.TryRead(.(data, iconEntry.mBytesInRes))); + + String bmpPath = scope $"@{(int)(void*)data:X}:{iconEntry.mBytesInRes}.bmp"; + + var image = Image.LoadFromFile(bmpPath); + if (image != null) + return image; + + return .Err; + } + public Result AddIcon(String iconFile) { if (!iconFile.EndsWith(".ico", .OrdinalIgnoreCase))