В этой статье мы разберем реализацию простого файлового менеджера на Object Pascal с использованием библиотеки Raylib, особое внимание уделив поддержке различных языков, включая кириллицу.
Введение в Raylib
Raylib - это простая и легкая библиотека для создания игр и мультимедийных приложений, которая поддерживает множество платформ. Она предоставляет удобный API для работы с графикой, звуком и вводом.
Структура файлового менеджера
Рассмотрим основные компоненты файлового менеджера:
program filebrowser;
{$WARN 5044 off : Symbol "$1" is not portable}
uses
SysUtils, Raylib, math;
const
SCREEN_WIDTH = 800;
SCREEN_HEIGHT = 600;
FONT_SIZE = 20;
ITEM_HEIGHT = 30;
MAX_VISIBLE_ITEMS = 15;
MARGIN = 10;
SCROLL_SPEED = 1;
var
Files: array of string;
ScrollOffset: Integer = 0;
SelectedItem: Integer = 0;
CurrentDir, ParentDir: string;
FileListArea: TRectangle;
RussianFont: TFont;
Загрузка шрифтов с поддержкой кириллицы
Одна из ключевых особенностей этого примера - корректное отображение текста на разных языках. Рассмотрим функцию загрузки шрифта:
function LoadRussianFont: TFont;
var
i: Integer;
cp: array[0..511] of Integer;
begin
// Инициализируем массив кодовых точек
for i := 0 to 95 do
cp[i] := 32 + i; // Базовые ASCII символы
// Добавляем кириллические символы (диапазон 0x0400-0x04FF)
for i := 0 to 255 do
cp[96 + i] := $400 + i;
// Загружаем шрифт с указанными кодовыми точками
{$IFDEF UNIX}
Result := LoadFontEx('Yulong-Regular.otf', FONT_SIZE, @cp[0], Length(cp));
{$ELSE}
Result := LoadFontEx('C:\Windows\Fonts\arial.ttf', FONT_SIZE, @cp[0], Length(cp));
{$ENDIF}
if Result.texture.id = 0 then // Если шрифт не загрузился, используем стандартный
Result := GetFontDefault();
end;
Улучшенная версия с поддержкой множества языков
В более поздней версии автор предложил улучшенную функцию загрузки шрифтов, поддерживающую больше языков:
function LoadUnicodeFont: TFont;
var
cp: array of Integer;
i, count: Integer;
procedure AddRange(start, stop: Integer);
begin
while start <= stop do
begin
if count >= Length(cp) then
SetLength(cp, Length(cp) + 256);
cp[count] := start;
Inc(count);
Inc(start);
end;
end;
begin
// Создаем массив кодовых точек
SetLength(cp, 4096);
count := 0;
// Базовые символы ASCII
AddRange(32, 126);
// Основные европейские языки
AddRange($C0, $17F); // Latin-1 Supplement + Latin Extended-A
AddRange($180, $24F); // Latin Extended-B
AddRange($370, $3FF); // Greek and Coptic
AddRange($400, $4FF); // Cyrillic
AddRange($500, $52F); // Cyrillic Supplement
AddRange($1E00, $1EFF); // Latin Extended Additional
// Специальные символы для конкретных языков
// Африканские языки
AddRange($1E00, $1EFF); // Latin Extended Additional (Fon, Ewe и др.)
AddRange($2C60, $2C7F); // Latin Extended-C (Aja, Basaa)
// Азиатские языки (кириллица и латиница)
AddRange($A640, $A69F); // Cyrillic Extended-B (Kazakh, Chuvash)
// Дополнительные символы
AddRange($300, $36F); // Combining Diacritical Marks
AddRange($1DC0, $1DFF); // Combining Diacritical Marks Supplement
// Уменьшаем массив до фактического размера
SetLength(cp, count);
// Пытаемся загрузить универсальный шрифт
{$IFDEF UNIX}
Result := LoadFontEx('Yulong-Regular.otf', FONT_SIZE, @cp[0], Length(cp));
if Result.texture.id = 0 then
Result := LoadFontEx('/usr/share/fonts/truetype/freefont/FreeSans.ttf', FONT_SIZE, @cp[0], Length(cp));
{$ELSE}
Result := LoadFontEx('C:\Windows\Fonts\arialuni.ttf', FONT_SIZE, @cp[0], Length(cp));
if Result.texture.id = 0 then
Result := LoadFontEx('C:\Windows\Fonts\arial.ttf', FONT_SIZE, @cp[0], Length(cp));
{$ENDIF}
if Result.texture.id = 0 then // Если шрифт не загрузился, используем стандартный
Result := GetFontDefault();
end;
Работа с файловой системой
Функция загрузки содержимого директории:
procedure LoadDirectory(const Path: string);
var
SearchRec: TSearchRec;
FileCount: Integer = 0;
IsDirectory: Boolean;
begin
SetLength(Files, 0);
if Path <> ExtractFileDrive(Path) then
begin
SetLength(Files, 1);
Files[0] := '..' + PathDelim;
FileCount := 1;
end;
if FindFirst(IncludeTrailingPathDelimiter(Path) + '*', faDirectory, SearchRec) = 0 then
begin
repeat
IsDirectory := (SearchRec.Attr and faDirectory) <> 0;
if IsDirectory and (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
begin
SetLength(Files, FileCount + 1);
Files[FileCount] := SearchRec.Name + PathDelim;
Inc(FileCount);
end;
until FindNext(SearchRec) <> 0;
FindClose(SearchRec);
end;
if FindFirst(IncludeTrailingPathDelimiter(Path) + '*', faAnyFile and not (faDirectory or faHidden), SearchRec) = 0 then
begin
repeat
IsDirectory := (SearchRec.Attr and faDirectory) <> 0;
if not IsDirectory then
begin
SetLength(Files, FileCount + 1);
Files[FileCount] := SearchRec.Name;
Inc(FileCount);
end;
until FindNext(SearchRec) <> 0;
FindClose(SearchRec);
end;
CurrentDir := IncludeTrailingPathDelimiter(Path);
ScrollOffset := 0;
SelectedItem := 0;
end;
Отрисовка интерфейса
Процедура отрисовки пользовательского интерфейса:
procedure DrawInterface;
var
i, YPos: Integer;
FileName: string;
MaxVisibleItems: Integer;
BGColor: TColor;
begin
DrawTextEx(RussianFont, 'Файловый браузер',
Vector2Create(MARGIN, MARGIN), FONT_SIZE , 1, DARKGRAY);
// Текущая директория
DrawTextEx(RussianFont, PChar('Директория: ' + CurrentDir),
Vector2Create(MARGIN, MARGIN + 40), FONT_SIZE, 1, GRAY);
FileListArea := RectangleCreate(MARGIN, MARGIN + 80, SCREEN_WIDTH - 2*MARGIN, SCREEN_HEIGHT - 160);
DrawRectangleLinesEx(FileListArea, 1, GRAY);
MaxVisibleItems := Trunc(FileListArea.height / ITEM_HEIGHT);
if SelectedItem < ScrollOffset then
ScrollOffset := SelectedItem;
if SelectedItem >= ScrollOffset + MaxVisibleItems then
ScrollOffset := SelectedItem - MaxVisibleItems + 1;
for i := 0 to Min(MaxVisibleItems - 1, High(Files) - ScrollOffset) do
begin
YPos := Round(FileListArea.y) + i * ITEM_HEIGHT;
FileName := Files[ScrollOffset + i];
// Чередование цветов фона
if (ScrollOffset + i) mod 2 = 0 then
BGColor := Fade(LIGHTGRAY, 0.5)
else
BGColor := Fade(LIGHTGRAY, 0.3);
if (ScrollOffset + i) = SelectedItem then
begin
DrawRectangle(Round(FileListArea.x), YPos, Round(FileListArea.width), ITEM_HEIGHT, BLUE);
if (FileName <> '') and (FileName[Length(FileName)] = PathDelim) then
DrawTextEx(RussianFont, PChar('['+ExcludeTrailingBackslash(FileName)+']'),
Vector2Create(Round(FileListArea.x) + 5, YPos + 5), FONT_SIZE, 1, WHITE)
else
DrawTextEx(RussianFont, PChar(FileName),
Vector2Create(Round(FileListArea.x) + 5, YPos + 5), FONT_SIZE, 1, WHITE)
end
else
begin
DrawRectangle(Round(FileListArea.x), YPos, Round(FileListArea.width), ITEM_HEIGHT, BGColor);
if (FileName <> '') and (FileName[Length(FileName)] = PathDelim) then
begin
if FileName = '..' + PathDelim then
DrawTextEx(RussianFont, PChar('['+ExcludeTrailingBackslash(FileName)+']'),
Vector2Create(Round(FileListArea.x) + 5, YPos + 5), FONT_SIZE, 1, DARKBLUE)
else
DrawTextEx(RussianFont, PChar('['+ExcludeTrailingBackslash(FileName)+']'),
Vector2Create(Round(FileListArea.x) + 5, YPos + 5), FONT_SIZE, 1, DARKGREEN);
end
else
DrawTextEx(RussianFont, PChar(FileName),
Vector2Create(Round(FileListArea.x) + 5, YPos + 5), FONT_SIZE, 1, DARKGRAY);
end;
end;
// Информация о файлах
if Length(Files) > 0 then
DrawTextEx(RussianFont, PChar(Format('%d/%d', [SelectedItem + 1, Length(Files)])),
Vector2Create(SCREEN_WIDTH - 50, SCREEN_HEIGHT - 30), FONT_SIZE, 1, LIGHTGRAY);
DrawTextEx(RussianFont, 'Стрелки:Навигация Enter:Открыть Backspace:Назад',
Vector2Create(MARGIN, SCREEN_HEIGHT - 30), FONT_SIZE , 1, GRAY);
end;
Основной цикл программы
Главный цикл обработки событий и управления программой:
begin
InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, 'Файловый браузер');
SetTargetFPS(60);
// Загружаем шрифт с поддержкой кириллицы
RussianFont := LoadRussianFont;
SetTextureFilter(RussianFont.texture, TEXTURE_FILTER_TRILINEAR);
// Загрузка начальной директории
LoadDirectory(GetUserHomeDir);
while not WindowShouldClose do
begin
HandleMouseWheel;
if IsKeyPressed(KEY_DOWN) and (SelectedItem < High(Files)) then
Inc(SelectedItem);
if IsKeyPressed(KEY_UP) and (SelectedItem > 0) then
Dec(SelectedItem);
if IsKeyPressed(KEY_PAGE_DOWN) then
SelectedItem := Min(SelectedItem + MAX_VISIBLE_ITEMS, High(Files));
if IsKeyPressed(KEY_PAGE_UP) then
SelectedItem := Max(SelectedItem - MAX_VISIBLE_ITEMS, 0);
if IsKeyPressed(KEY_ENTER) and (Length(Files) > 0) then
begin
if (Files[SelectedItem] <> '') and (Files[SelectedItem][Length(Files[SelectedItem])] = PathDelim) then
begin
if Files[SelectedItem] = '..' + PathDelim then
begin
ParentDir := ExtractFileDir(ExcludeTrailingPathDelimiter(CurrentDir));
if ParentDir <> CurrentDir then
LoadDirectory(ParentDir);
end
else
LoadDirectory(CurrentDir + Files[SelectedItem]);
end
else
TraceLog(LOG_INFO, PChar('Selected: ' + CurrentDir + Files[SelectedItem]));
end;
if IsKeyPressed(KEY_BACKSPACE) then
begin
ParentDir := ExtractFileDir(ExcludeTrailingPathDelimiter(CurrentDir));
if ParentDir <> CurrentDir then
LoadDirectory(ParentDir);
end;
if IsKeyPressed(KEY_F5) then
LoadDirectory(CurrentDir);
BeginDrawing();
ClearBackground(RAYWHITE);
DrawInterface;
EndDrawing();
end;
// Освобождаем шрифт
UnloadFont(RussianFont);
CloseWindow;
end.
Альтернативные решения
Использование Free Pascal Components (FPC): Вместо Raylib можно использовать стандартные компоненты Lazarus или другие библиотеки, такие как LCL или fpGUI.
Кроссплатформенные решения: Для более сложных файловых менеджеров можно рассмотреть использование:
GTK или Qt через привязки к Pascal
Electron с использованием Pas2JS
Дополнительные функции:
Добавление вкладок для работы с несколькими директориями
Поддержка Drag&Drop
Встроенный просмотрщик файлов
Заключение
Представленный пример демонстрирует, как создать простой, но функциональный файловый менеджер на Pascal с использованием библиотеки Raylib. Особое внимание уделено поддержке различных языков, что делает приложение более универсальным. Код может служить хорошей основой для более сложных проектов.
Пример файлового менеджера на Pascal с использованием Raylib, демонстрирующий загрузку и отображение языков.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.