Skip to content

Commit

Permalink
add texture tool
Browse files Browse the repository at this point in the history
  • Loading branch information
ArthurHeitmann committed Sep 7, 2024
1 parent 22cf218 commit f61bc98
Show file tree
Hide file tree
Showing 16 changed files with 499 additions and 64 deletions.
33 changes: 25 additions & 8 deletions lib/fileTypeUtils/textures/ddsConverter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@ import '../../stateManagement/events/statusInfo.dart';
import '../../utils/assetDirFinder.dart';
import '../../utils/utils.dart';

Future<void> pngToDds(String ddsPath, String pngPath) async {
Future<void> texToDds(String srcPath, String dstPath, { String compression = "dxt5", int mipmaps = 0 }) async {
var result = await Process.run(
magickBinPath!,
[pngPath, "-define", "dds:mipmaps=0", ddsPath]
[
srcPath,
if (dstPath.endsWith(".dds")) ...[
"-define",
"dds:compression=$compression",
"-define",
"dds:mipmaps=$mipmaps",
],
dstPath
]
);
if (result.exitCode != 0) {
messageLog.add("stdout: ${result.stdout}");
Expand All @@ -19,22 +28,30 @@ Future<void> pngToDds(String ddsPath, String pngPath) async {
}
}

Future<Uint8List?> ddsToPng(String ddsPath, [String? pngPath]) async {
Future<Uint8List?> texToPng(String ddsPath, {String? pngPath, int? maxHeight, bool verbose = true}) async {
if (!await hasMagickBins()) {
showToast("Can't load texture because ImageMagick is not found.");
if (verbose)
showToast("Can't load texture because ImageMagick is not found.");
if (pngPath == null)
return null;
throw Exception("Can't load texture because ImageMagick is not found.");
}
var result = await Process.run(
magickBinPath!,
["DDS:$ddsPath", pngPath == null ? "PNG:-" : "PNG:$pngPath"],
[
ddsPath,
if (maxHeight != null) ...[
"-resize", "x$maxHeight",
],
pngPath == null ? "PNG:-" : "PNG:$pngPath",
],
stdoutEncoding: pngPath == null ? null : systemEncoding,
stderrEncoding: systemEncoding,
);
if (result.exitCode != 0) {
showToast("Can't load texture because ImageMagick failed to convert DDS to PNG.");
if (pngPath == null)
return null;
if (verbose)
showToast("Can't load texture because ImageMagick failed to convert DDS to PNG.");
print("stdout: ${result.stderr}");
throw Exception("Can't load texture because ImageMagick failed to convert DDS to PNG.");
}
if (pngPath == null)
Expand Down
4 changes: 2 additions & 2 deletions lib/stateManagement/openFiles/types/FtbFileData.dart
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ class FtbData extends ChangeNotifier {
imgOperations,
);
var atlasInfo = await runFontAtlasGenerator(cliArgs);
await pngToDds(ddsPath, texPngPath);
await texToDds(texPngPath, ddsPath);

messageLog.add("Generated font atlas $batchI with ${atlasInfo.symbols.length} symbols");

Expand Down Expand Up @@ -457,7 +457,7 @@ class FtbData extends ChangeNotifier {
await File(ddsSavePath).writeAsBytes(texBytes);
// convert dds to png
var pngSavePath = join(extractDir, "$i.png");
await ddsToPng(ddsSavePath, pngSavePath);
await texToPng(ddsSavePath, pngPath: pngSavePath);
textures[i].extractedPngPath = pngSavePath;
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/stateManagement/openFiles/types/McdFileData.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1152,7 +1152,7 @@ class McdData extends _McdFilePart {

// save texture
var texDdsPath = "${texPngPath.substring(0, texPngPath.length - 3)}dds";
await pngToDds(texDdsPath, texPngPath);
await texToDds(texPngPath, texDdsPath);
await File(texDdsPath).rename(textureWtpPath.value);
var texFileSize = await File(textureWtpPath.value).length();

Expand Down
13 changes: 13 additions & 0 deletions lib/utils/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,13 @@ Future<void> waitForNextFrame() {
return completer.future;
}

({String msg, int time})? lastToast;
void showToast(String msg, [Duration duration = const Duration(seconds: 4)]) {
var now = DateTime.now().millisecondsSinceEpoch;
if (lastToast?.msg == msg && now - lastToast!.time < duration.inMilliseconds) {
lastToast = (msg: msg, time: now);
return;
}
print("showToast: $msg");
messageLog.add(msg);
FToast toast = FToast();
Expand Down Expand Up @@ -810,3 +816,10 @@ String trimFilePath(String path, int maxLength) {
}
return "...$sep${usedParts.join(sep)}";
}

void debugOnly(void Function() func) {
assert(() {
func();
return true;
}());
}
3 changes: 2 additions & 1 deletion lib/widgets/EditorLayout.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';

import '../utils/utils.dart';
import 'FileHierarchyExplorer/FileExplorer.dart';
import 'FileHierarchyExplorer/fileMetaEditor.dart';
import 'filesView/OpenFilesAreas.dart';
Expand All @@ -18,7 +19,7 @@ class EditorLayout extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Sidebar(
initialWidth: MediaQuery.of(context).size.width * 0.22,
initialWidth: clamp(MediaQuery.of(context).size.width * 0.3, 380, 440),
entries: [
SidebarEntryConfig(
name: "Files",
Expand Down
22 changes: 8 additions & 14 deletions lib/widgets/FileHierarchyExplorer/FileExplorer.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

import 'dart:io';

import 'package:desktop_drop/desktop_drop.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';

Expand All @@ -12,6 +11,7 @@ import '../../stateManagement/preferencesData.dart';
import '../../utils/utils.dart';
import '../../widgets/theme/customTheme.dart';
import '../misc/ChangeNotifierWidget.dart';
import '../misc/dropTargetBuilder.dart';
import '../misc/selectionPopup.dart';
import '../propEditors/UnderlinePropTextField.dart';
import '../propEditors/propEditorFactory.dart';
Expand All @@ -26,14 +26,13 @@ class FileExplorer extends ChangeNotifierWidget {
}

class _FileExplorerState extends ChangeNotifierState<FileExplorer> {
bool isDroppingFile = false;
bool expandSearch = false;

void openFile(DropDoneDetails details) async {
void openFile(List<String> files) async {
List<Future<HierarchyEntry?>> futures = [];

for (var file in details.files) {
futures.add(openHierarchyManager.openFile(file.path));
for (var file in files) {
futures.add(openHierarchyManager.openFile(file));
}

var openedFiles = await Future.wait(futures);
Expand All @@ -57,14 +56,9 @@ class _FileExplorerState extends ChangeNotifierState<FileExplorer> {

@override
Widget build(BuildContext context) {
return DropTarget(
onDragEntered: (details) => setState(() => isDroppingFile = true),
onDragExited: (details) => setState(() => isDroppingFile = false),
onDragDone: (details) {
isDroppingFile = false;
openFile(details);
},
child: Column(
return DropTargetBuilder(
onDrop: (files) => openFile(files),
builder: (context, isDropping) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Divider(height: 1),
Expand All @@ -78,7 +72,7 @@ class _FileExplorerState extends ChangeNotifierState<FileExplorer> {
const Center(
child: Text("No files open"),
),
if (isDroppingFile)
if (isDropping)
makeItemHoveredIndicator(context),
],
),
Expand Down
58 changes: 29 additions & 29 deletions lib/widgets/filesView/fileTabView.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import 'dart:io';
import 'dart:math';

import 'package:cross_file/cross_file.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart' as path;
Expand All @@ -16,6 +14,8 @@ import '../../stateManagement/openFiles/openFilesManager.dart';
import '../../utils/utils.dart';
import '../../widgets/theme/customTheme.dart';
import '../misc/ChangeNotifierWidget.dart';
import '../misc/dropTargetBuilder.dart';
import '../misc/indexedStackIsVisible.dart';
import 'FileTabEntry.dart';
import 'FileType.dart';

Expand All @@ -33,7 +33,6 @@ class FileTabView extends ChangeNotifierWidget {
class _FileTabViewState extends ChangeNotifierState<FileTabView> {
ScrollController tabBarScrollController = ScrollController();
String? prevActiveUuid;
bool isDroppingFile = false;

@override
void onNotified() {
Expand Down Expand Up @@ -69,23 +68,24 @@ class _FileTabViewState extends ChangeNotifierState<FileTabView> {
tabBarScrollController.animateTo(tabEnd - viewWidth, duration: const Duration(milliseconds: 250), curve: Curves.ease);
}

void openFiles(List<XFile> files) async {
void openFiles(List<String> files) async {
if (files.isEmpty)
return;
OpenFileData? firstFile;
int openedFiles = 0;
const fileExplorerExtensions = { ".pak", ...datExtensions, ".yax", ".bin", ".wai", ".wsp", ...bxmExtensions, ".bnk", ".cpk" };
for (var file in files) {
bool isSaveSlotData = file.name.startsWith("SlotData_") && file.name.endsWith(".dat");
if (fileExplorerExtensions.contains(path.extension(file.name)) && !isSaveSlotData || await Directory(file.path).exists()) {
var entry = await openHierarchyManager.openFile(file.path);
var fileName = path.basename(file);
bool isSaveSlotData = fileName.startsWith("SlotData_") && fileName.endsWith(".dat");
if (fileExplorerExtensions.contains(path.extension(fileName)) && !isSaveSlotData || await Directory(file).exists()) {
var entry = await openHierarchyManager.openFile(file);
if (entry?.isOpenable == true)
entry!.onOpen();
if (entry != null)
openedFiles++;
continue;
}
var newFileData = areasManager.openFile(file.path, toArea: widget.viewArea);
var newFileData = areasManager.openFile(file, toArea: widget.viewArea);
openedFiles++;
firstFile ??= newFileData;
}
Expand All @@ -99,20 +99,15 @@ class _FileTabViewState extends ChangeNotifierState<FileTabView> {

@override
Widget build(BuildContext context) {
return DropTarget(
onDragEntered: (details) => setState(() => isDroppingFile = true),
onDragExited: (details) => setState(() => isDroppingFile = false),
onDragDone: (details) {
isDroppingFile = false;
openFiles(details.files);
},
child: setupShortcuts(
return DropTargetBuilder(
onDrop: openFiles,
builder: (context, isDropping) => setupShortcuts(
child: Stack(
children: [
Positioned.fill(
child: widget.viewArea.currentFile.value != null
? makeFilesStack(context)
: makeEmptyTab(context),
: makeEmptyTab(context, isDropping),
),
makeTabBar(),
],
Expand All @@ -130,21 +125,26 @@ class _FileTabViewState extends ChangeNotifierState<FileTabView> {
}

Widget makeFilesStack(BuildContext context) {
var currentFileIndex = widget.viewArea.files.indexOf(widget.viewArea.currentFile.value!);
return IndexedStack(
index: widget.viewArea.files.indexOf(widget.viewArea.currentFile.value!),
children: widget.viewArea.files.map((file) =>
ConstrainedBox(
key: Key(file.uuid),
constraints: const BoxConstraints.expand(),
child: FocusTraversalGroup(
child: makeFileEditor(file),
),
)
).toList(),
index: currentFileIndex,
children: [
for (var (i, file) in widget.viewArea.files.indexed)
IndexedStackIsVisible(
isVisible: i == currentFileIndex,
child: ConstrainedBox(
key: Key(file.uuid),
constraints: const BoxConstraints.expand(),
child: FocusTraversalGroup(
child: makeFileEditor(file),
),
),
)
],
);
}

Widget makeEmptyTab(BuildContext context) {
Widget makeEmptyTab(BuildContext context, bool isDropping) {
return Center(
child: ConstrainedBox(
constraints: BoxConstraints.loose(const Size(150, 150)),
Expand All @@ -154,7 +154,7 @@ class _FileTabViewState extends ChangeNotifierState<FileTabView> {
duration: const Duration(milliseconds: 250),
turns: Random().nextInt(100) == 69 ? 1 : 0,
child: AnimatedScale(
scale: isDroppingFile ? 1.25 : 1.0,
scale: isDropping ? 1.25 : 1.0,
duration: const Duration(milliseconds: 250),
curve: const _BezierCurve(0, 1.5, 1.2, 1),
child: Image(
Expand Down
2 changes: 1 addition & 1 deletion lib/widgets/filesView/types/fonts/McdFontDebugger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class _McdFontDebuggerState extends ChangeNotifierState<McdFontDebugger> {
setState(() {});
}
else if (widget.texturePath.endsWith(".dds") || widget.texturePath.endsWith(".wtp")) {
var imageBytes = await ddsToPng(widget.texturePath);
var imageBytes = await texToPng(widget.texturePath);
if (imageBytes == null)
return;
image = Image.memory(imageBytes, fit: BoxFit.contain);
Expand Down
1 change: 1 addition & 0 deletions lib/widgets/filesView/types/genericTable/tableEditor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ class _TableEditorState extends ChangeNotifierState<TableEditor> {
controller: controller,
physics: physics,
itemCount: rows.length,
cacheExtent: 4000,
itemBuilder: (context, i) => _TableRow(i % 2 == 1, rows[i].index, rows[i].rowConfig, widget.config),
);
}
Expand Down
9 changes: 8 additions & 1 deletion lib/widgets/layout/sidebar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/material.dart';

import '../../main.dart';
import '../../utils/utils.dart';
import '../misc/indexedStackIsVisible.dart';
import '../theme/customTheme.dart';

class SidebarEntryConfig {
Expand Down Expand Up @@ -125,7 +126,13 @@ class _SidebarState extends State<Sidebar> with RouteAware {
link: _layerLink,
child: IndexedStack(
index: _selectedIndex,
children: widget.entries.map((e) => e.child).toList(),
children: [
for (var (i, entry) in widget.entries.indexed)
IndexedStackIsVisible(
isVisible: i == _selectedIndex,
child: entry.child,
)
]
),
),
),
Expand Down
32 changes: 32 additions & 0 deletions lib/widgets/misc/dropTargetBuilder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/material.dart';

import 'indexedStackIsVisible.dart';

class DropTargetBuilder extends StatefulWidget {
final void Function(List<String> paths) onDrop;
final Widget Function(BuildContext context, bool isDropping) builder;

const DropTargetBuilder({super.key, required this.onDrop, required this.builder});

@override
State<DropTargetBuilder> createState() => _DropTargetBuilderState();
}

class _DropTargetBuilderState extends State<DropTargetBuilder> {
bool isDropping = false;

@override
Widget build(BuildContext context) {
return DropTarget(
enable: ModalRoute.of(context)!.isCurrent && IndexedStackIsVisible.of(context) != false,
onDragEntered: (_) => setState(() => isDropping = true),
onDragExited: (_) => setState(() => isDropping = false),
onDragDone: (details) {
widget.onDrop(details.files.map((f) => f.path).toList());
},
child: widget.builder(context, isDropping),
);
}
}
Loading

0 comments on commit f61bc98

Please sign in to comment.