feat: start adding image viewer
This commit is contained in:
parent
1c50528fee
commit
9c8f07e3a7
|
@ -12,6 +12,7 @@ using FFXIVClientStructs.FFXIV.Client.UI;
|
|||
using Newtonsoft.Json;
|
||||
using Screenie.Ui;
|
||||
using WebP.Net;
|
||||
using WebPDotNet;
|
||||
|
||||
namespace Screenie;
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
internal enum DrawStatus {
|
||||
Continuing,
|
||||
Finished,
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
internal interface IDrawable : IDisposable {
|
||||
DrawStatus Draw();
|
||||
}
|
|
@ -8,6 +8,8 @@ namespace Screenie.Ui;
|
|||
internal class PluginUi : IDisposable {
|
||||
private Plugin Plugin { get; }
|
||||
private List<ITab> Tabs { get; } = [];
|
||||
internal List<IDrawable> Drawables { get; } = [];
|
||||
private List<IDrawable> ToDispose { get; } = [];
|
||||
|
||||
internal bool Visible;
|
||||
|
||||
|
@ -36,6 +38,31 @@ internal class PluginUi : IDisposable {
|
|||
}
|
||||
|
||||
private void Draw() {
|
||||
foreach (var drawable in this.ToDispose) {
|
||||
try {
|
||||
drawable.Dispose();
|
||||
} catch (Exception ex) {
|
||||
Plugin.Log.Error(ex, "Failed to dispose drawable");
|
||||
}
|
||||
}
|
||||
|
||||
this.ToDispose.Clear();
|
||||
|
||||
this.Drawables.RemoveAll(drawable => {
|
||||
// return true to stop drawing
|
||||
try {
|
||||
if (drawable.Draw() == DrawStatus.Finished) {
|
||||
this.ToDispose.Add(drawable);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (Exception ex) {
|
||||
Plugin.Log.Error(ex, "Error drawing drawable");
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.Visible) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -191,7 +191,7 @@ internal class DatabaseTab : ITab {
|
|||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("View", new Vector2(buttonWidth, 0))) {
|
||||
// TODO: make image viewer
|
||||
this.Plugin.Ui.Drawables.Add(new Viewer(this.Plugin, shot.Path));
|
||||
// TODO: draw dots where players are (on hover?)
|
||||
}
|
||||
|
||||
|
@ -200,8 +200,27 @@ internal class DatabaseTab : ITab {
|
|||
ImGui.TextUnformatted("None");
|
||||
}
|
||||
|
||||
var viewers = this.Plugin.Ui.Drawables
|
||||
.Where(drawable => drawable is Viewer)
|
||||
.Cast<Viewer>()
|
||||
.Where(viewer => viewer.ImagePath == shot.Path)
|
||||
.ToList();
|
||||
var anyHovered = false;
|
||||
foreach (var chara in shot.Metadata.VisibleCharacters) {
|
||||
ImGui.TextUnformatted($"{chara.Name} ({chara.HomeWorld})");
|
||||
|
||||
if (ImGui.IsItemHovered()) {
|
||||
foreach (var viewer in viewers) {
|
||||
anyHovered = true;
|
||||
viewer.DotPosition = chara.ImagePosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyHovered) {
|
||||
foreach (var viewer in viewers) {
|
||||
viewer.DotPosition = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Utility;
|
||||
using ImGuiNET;
|
||||
using Screenie.Util;
|
||||
|
||||
namespace Screenie.Ui;
|
||||
|
||||
internal class Viewer : IDrawable {
|
||||
private Plugin Plugin { get; }
|
||||
private Guid Id { get; } = Guid.NewGuid();
|
||||
private IDalamudTextureWrap? Image { get; set; }
|
||||
private Exception? Error { get; set; }
|
||||
private bool _disposed;
|
||||
private bool _visible;
|
||||
internal string ImagePath { get; }
|
||||
internal Vector2? DotPosition { get; set; }
|
||||
|
||||
internal Viewer(Plugin plugin, string path) {
|
||||
this.Plugin = plugin;
|
||||
this.ImagePath = path;
|
||||
|
||||
Task.Factory.StartNew(async () => {
|
||||
try {
|
||||
var bytes = await File.ReadAllBytesAsync(this.ImagePath);
|
||||
this.Image = await ImageHelper.LoadImageAsync(this.Plugin.Interface.UiBuilder, bytes);
|
||||
} catch (Exception ex) {
|
||||
this.Error = ex;
|
||||
Plugin.Log.Warning(ex, $"Failed to load image at {this.ImagePath}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._disposed = true;
|
||||
|
||||
// if (this.Image != null) {
|
||||
// this.Plugin.Framework.RunOnTick(this.Image.Dispose);
|
||||
// }
|
||||
this.Image?.Dispose();
|
||||
}
|
||||
|
||||
public DrawStatus Draw() {
|
||||
if (!this._visible) {
|
||||
return DrawStatus.Finished;
|
||||
}
|
||||
|
||||
ImGui.PushID($"viewer-{this.ImagePath}-{this.Id}");
|
||||
using var popId = new OnDispose(ImGui.PopID);
|
||||
|
||||
using var end = new OnDispose(ImGui.End);
|
||||
if (!ImGui.Begin($"View {Path.GetFileName(this.ImagePath)}", ref this._visible, ImGuiWindowFlags.NoSavedSettings)) {
|
||||
return DrawStatus.Continuing;
|
||||
}
|
||||
|
||||
var viewport = ImGui.GetWindowViewport();
|
||||
var viewportSize = viewport.Size;
|
||||
|
||||
ImGui.SetWindowSize(viewportSize * new Vector2(0.20f), ImGuiCond.Appearing);
|
||||
|
||||
if (this.Error != null) {
|
||||
ImGuiHelpers.CenteredText("An error ocurred loading the image.");
|
||||
|
||||
ImGui.PushFont(UiBuilder.MonoFont);
|
||||
using var popFont = new OnDispose(ImGui.PopFont);
|
||||
|
||||
ImGui.TextUnformatted(this.Error.Message);
|
||||
}
|
||||
|
||||
if (this.Image != null) {
|
||||
var beforeImage = ImGui.GetCursorScreenPos();
|
||||
var avail = ImGui.GetContentRegionAvail();
|
||||
var imageSize = this.Image.Size;
|
||||
|
||||
var constrainedX = avail.X < imageSize.X;
|
||||
var constrainedY = avail.Y < imageSize.Y;
|
||||
|
||||
var sizeToUse = imageSize;
|
||||
if (constrainedX || constrainedY) {
|
||||
// var dividend = imageSize / avail;
|
||||
// var inv = Vector2.One / dividend;
|
||||
// scale the image based on the more-constrained dimension
|
||||
// var ratio = dividend.X > dividend.Y
|
||||
// ? inv.X // X is more constrained
|
||||
// : inv.Y; // Y is more constrained
|
||||
|
||||
var dividend = avail / imageSize;
|
||||
// scale the image based on the more-constrained dimension
|
||||
var ratio = dividend.Y > dividend.X
|
||||
? dividend.X // X is more constrained
|
||||
: dividend.Y; // Y is more constrained
|
||||
sizeToUse = new Vector2(
|
||||
imageSize.X * ratio,
|
||||
imageSize.Y * ratio
|
||||
);
|
||||
}
|
||||
|
||||
ImGui.Image(this.Image.ImGuiHandle, sizeToUse);
|
||||
}
|
||||
|
||||
return DrawStatus.Continuing;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Internal;
|
||||
|
||||
namespace Screenie.Util;
|
||||
|
||||
internal static class ImageHelper {
|
||||
internal static async Task<IDalamudTextureWrap?> LoadImageAsync(UiBuilder builder, byte[] buffer) {
|
||||
if (buffer.Length >= 12 && buffer.AsSpan()[..4].SequenceEqual("RIFF"u8) && buffer.AsSpan()[8..12].SequenceEqual("WEBP"u8)) {
|
||||
return await WebPHelper.LoadAsync(builder, buffer);
|
||||
}
|
||||
|
||||
return await builder.LoadImageAsync(buffer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Internal;
|
||||
using WebP.Net;
|
||||
|
||||
namespace Screenie.Util;
|
||||
|
||||
internal static class WebPHelper {
|
||||
internal static async Task<IDalamudTextureWrap?> LoadAsync(UiBuilder builder, byte[] imageBytes) {
|
||||
using var webp = new WebPObject(imageBytes);
|
||||
var info = webp.GetInfo();
|
||||
var outputBuffer = webp.GetWebPLossy(80);
|
||||
|
||||
return await builder.LoadImageRawAsync(outputBuffer, info.Width, info.Height, info.HasAlpha ? 4 : 3);
|
||||
}
|
||||
}
|
BIN
database.sqlite
BIN
database.sqlite
Binary file not shown.
Loading…
Reference in New Issue