Files

334 lines
11 KiB
Go

package editor
import (
"aego/art"
"aego/asset"
"aego/asset/importers/artsource"
"aego/asset/pipeline"
"aego/core/mathx"
"aego/editor/core/context"
"aego/editor/core/document"
"aego/editor/core/services"
"aego/editor/core/workspace"
"aego/editor/ext"
"aego/engine/i18n"
"aego/gpu"
"aego/platform/input"
"aego/render/texture"
"aego/ui/widgets"
)
// Dock panels follow the focused art document. All edits use that document's
// canonical binding and history; panels never own a second document/session.
type artDocks struct {
reg *ext.Registry
active *artPanel
preview imageDisplay
viewport imageViewport
pixels *imagePixels
owner document.ID
revision, device uint64
frame art.ID
isolation art.ID
channel art.Channel
draft bool
mode int
surface imagePreview
animationClipboard *art.AnimationClipboard
assetPreview *artDocks
}
func newArtDocks(r *ext.Registry) (*artDocks, error) {
d := &artDocks{reg: r, assetPreview: &artDocks{reg: r, mode: 1}}
for _, part := range []struct {
id string
slot workspace.Slot
}{
{"art.tools", workspace.SlotLeft}, {"art.options", workspace.SlotLeft},
{"art.properties", workspace.SlotRight}, {"art.palette", workspace.SlotRight},
{"art.color", workspace.SlotRight}, {"art.layers", workspace.SlotRight},
{"art.channels", workspace.SlotRight}, {"art.selection", workspace.SlotRight},
{"art.info", workspace.SlotRight}, {"art.timeline", workspace.SlotBottom},
{"art.preview", workspace.SlotCenter}, {"art.asset_preview", workspace.SlotRight},
} {
if err := r.Panel(workspace.Desc{Panel: &artDockPanel{d: d, id: part.id}, Slot: part.slot}); err != nil {
return nil, err
}
}
r.Tick(func() {
if d.active == nil {
return
}
if r.Editor().Docs.AssetDocument(d.active.doc.Asset) != d.active.doc {
d.active = nil
d.preview.close(r.Editor())
if d.surface.mat != nil || d.surface.pool != nil {
d.surface.close(r.Editor())
}
d.pixels = nil
d.owner = 0
d.assetPreview.closePreview(r.Editor())
} else if !r.PanelVisible("art.preview") {
d.preview.close(r.Editor())
if d.surface.mat != nil || d.surface.pool != nil {
d.surface.close(r.Editor())
}
}
if !r.PanelVisible("art.asset_preview") {
d.assetPreview.closePreview(r.Editor())
}
})
return d, nil
}
func (d *artDocks) closePreview(ed *context.Editor) {
if d == nil {
return
}
d.preview.close(ed)
if d.surface.mat != nil || d.surface.pool != nil {
d.surface.close(ed)
}
d.pixels, d.active = nil, nil
d.owner = 0
d.viewport.Ready = false
}
type artDockPanel struct {
d *artDocks
id string
}
func (p *artDockPanel) ID() string { return p.id }
func (p *artDockPanel) Scope() string { return p.id }
func (p *artDockPanel) Icon() string {
switch p.id {
case "art.tools":
return "panel.art_tools"
case "art.timeline":
return "panel.art_timeline"
case "art.preview", "art.asset_preview":
return "anim.preview"
case "art.selection":
return "tool.selection"
case "art.info", "art.options":
return "panel.settings"
case "art.color":
return "tool.eyedropper"
default:
return "panel.art_tools"
}
}
func (p *artDockPanel) Title() string {
switch p.id {
case "art.tools":
return i18n.Tr("editor.art.tools", "Art Tools")
case "art.timeline":
return i18n.Tr("editor.art.panel_animation_timeline", "Animation Timeline")
case "art.preview":
return i18n.Tr("editor.art.animation_preview", "Animation Preview")
case "art.asset_preview":
return i18n.Tr("editor.art.panel_asset_preview", "Asset Preview in Aego")
case "art.options":
return i18n.Tr("editor.art.panel_tool_options", "Tool Options")
case "art.palette":
return i18n.Tr("editor.art.panel_palette", "Palette")
case "art.color":
return i18n.Tr("editor.art.panel_color", "Color Picker")
case "art.layers":
return i18n.Tr("editor.art.panel_layers", "Layers")
case "art.channels":
return i18n.Tr("editor.art.panel_channels", "Surface Channels")
case "art.selection":
return i18n.Tr("editor.art.panel_selection", "Selection & Transform")
case "art.info":
return i18n.Tr("editor.art.panel_info", "Art Properties")
default:
return i18n.Tr("editor.art.palette_layers", "Palette / Layers")
}
}
func (p *artDockPanel) Document() *document.AssetDocument {
if p.d.active != nil {
return p.d.active.doc
}
return nil
}
func (p *artDockPanel) Draw(f *services.Frame, area mathx.Rect) {
ui := f.UI
ui.PushID(p.id)
defer ui.PopID()
a := p.d.active
if a == nil || a.model == nil {
ui.Region(area)
defer ui.PopCursor()
widgets.LabelWrapped(ui, i18n.Tr("editor.art.open_hint", "Double-click a PNG or .aeart file in Assets to start drawing."), false)
return
}
editable := a.doc.Capabilities.Has(document.EditSource) && !a.model.PendingRecovery
switch p.id {
case "art.tools":
a.tools(ui, area)
case "art.properties":
a.properties(ui, area, editable)
case "art.palette":
a.propertyParts(ui, area, editable, p.id, artPartPalette)
case "art.layers":
a.propertyParts(ui, area, editable, p.id, artPartLayers)
case "art.channels":
a.propertyParts(ui, area, editable, p.id, artPartSurface)
case "art.selection":
a.propertyParts(ui, area, editable, p.id, artPartSelection)
case "art.info":
a.infoPanel(ui, area, editable)
case "art.options":
a.optionsPanel(ui, area, editable)
case "art.color":
if f.Focused && !ui.ModalActive() && !ui.AnyPopup() && !ui.WantsKeyboard() && ui.KeyPressed(input.KeyEscape) {
a.view.cancelOneShotPicker()
}
a.colorPanel(ui, area, editable)
case "art.timeline":
a.timeline(ui, area, editable)
case "art.asset_preview":
if p.d.assetPreview == nil {
p.d.assetPreview = &artDocks{reg: p.d.reg, mode: 1}
}
p.d.assetPreview.active = a
p.d.assetPreview.drawPreview(f, area)
default:
p.d.drawPreview(f, area)
}
}
func (d *artDocks) drawPreview(f *services.Frame, area mathx.Rect) {
a := d.active
s := a.model.Session
info := s.Info()
frame := s.Frame
if a.view.playing {
if v, err := s.Document.FrameAt(a.view.tag, int64((f.Now-a.view.playStart)*1000)); err == nil {
frame = v
}
}
device, lost := imageDevice(a.ctx)
if lost {
d.preview.close(a.ctx)
if d.surface.mat != nil || d.surface.pool != nil {
d.surface.close(a.ctx)
}
imagePreviewReason(f.UI, area, i18n.Tr("editor.art.preview_context_lost", "The graphics context was lost. Preview will resume when it is restored."))
return
}
if d.device != device {
d.preview.close(a.ctx)
if d.surface.mat != nil || d.surface.pool != nil {
d.surface.close(a.ctx)
}
d.device = device
}
if d.owner != a.doc.ID {
d.preview.close(a.ctx)
if d.surface.mat != nil || d.surface.pool != nil {
d.surface.close(a.ctx)
}
d.viewport.Ready = false
d.pixels = nil
d.owner = a.doc.ID
}
ui := f.UI
ui.Region(area)
defer ui.PopCursor()
cur := ui.Cursor()
widgets.Combo(ui, ui.ID("preview-mode"), cur.Next(0), &d.mode, []string{i18n.Tr("editor.art.pixel_preview", "Pixel preview"), i18n.Tr("editor.image.surface", "Lit surface")}, false)
if d.mode == 1 {
d.drawSurface(f, area, frame)
return
}
if d.surface.mat != nil || d.surface.pool != nil {
d.surface.close(a.ctx)
}
area = mathx.Rect{X: cur.X, Y: cur.Y, W: cur.W, H: max(0, area.Y+area.H-cur.Y-ui.Theme.Padding)}
if d.pixels == nil || d.revision != info.Document.Revision || d.frame != frame || d.channel != s.Channel || d.isolation != info.Isolation || info.Gesture || d.draft != info.Gesture {
pixels, err := s.Preview(frame, false, false)
if err != nil {
a.report(err)
return
}
d.pixels = &imagePixels{data: texture.Data{Width: pixels.Rect.Dx(), Height: pixels.Rect.Dy(), Format: gpu.TexSRGBA8, Pixels: pixels.Pix}, semantic: asset.TextureColor}
d.revision, d.frame, d.channel = info.Document.Revision, frame, s.Channel
d.isolation = info.Isolation
d.draft = info.Gesture
}
if !d.viewport.Ready {
d.viewport.fit(area, info.Document.Canvas.Width, info.Document.Canvas.Height)
}
drawImageCanvas(a.ctx, area, d.pixels, &d.preview, &d.viewport, imageRGBA, a.view.canvasBackground, false, [4]int{}, nil)
}
func (d *artDocks) drawSurface(f *services.Frame, area mathx.Rect, frame art.ID) {
a, ui := d.active, f.UI
cur := ui.Cursor()
if a.model.Draft {
content := mathx.Rect{X: cur.X, Y: cur.Y, W: cur.W, H: max(0, area.Y+area.H-cur.Y-ui.Theme.Padding)}
imagePreviewReason(ui, content, i18n.Tr("editor.art.png_draft_preview", "Save the drawing before previewing it as a game asset."))
return
}
d.surface.init()
c := &d.surface.controls
row := cur.Next(0)
for i, toggle := range []struct {
label string
value *bool
}{
{i18n.Tr("editor.image.lit", "Lit"), &c.Lit}, {i18n.Tr("editor.art.normal", "Normals"), &c.Normal}, {i18n.Tr("editor.art.emission", "Emission"), &c.Emission}, {"Bloom", &c.Bloom},
} {
artToggle(ui, "preview-"+toggle.label, artCell(row, i, 4), toggle.label, toggle.value)
}
row = cur.Next(0)
artToggle(ui, "preview-flip-x", artCell(row, 0, 3), i18n.Tr("editor.image.flip_x", "Flip X"), &c.FlipX)
artToggle(ui, "preview-flip-y", artCell(row, 1, 3), i18n.Tr("editor.image.flip_y", "Flip Y"), &c.FlipY)
widgets.DragFloat32(ui, ui.ID("preview-emission"), artCell(row, 2, 3), &c.EmissionStrength, widgets.NumberOpts{Speed: .05, Min: 0, HasMin: true, Max: 16, HasMax: true})
widgets.Tooltip(ui, ui.ID("preview-emission"), i18n.Tr("editor.art.preview_emission_strength", "Preview emission intensity; source materials remain unchanged"))
row = cur.Next(0)
for i, field := range []struct {
id, label string
value *float32
maximum float64
}{
{"light-height", i18n.Tr("editor.image.height", "Light height"), &c.Height, 10}, {"light-intensity", i18n.Tr("editor.image.intensity", "Light intensity"), &c.Intensity, 16}, {"light-ambient", i18n.Tr("editor.image.ambient", "Ambient intensity"), &c.Ambient, 4},
} {
widgets.DragFloat32(ui, ui.ID(field.id), artCell(row, i, 3), field.value, widgets.NumberOpts{Speed: .01, Min: 0, HasMin: true, Max: field.maximum, HasMax: true})
widgets.Tooltip(ui, ui.ID(field.id), field.label)
}
if a.doc.WorkingPreview.Pending || a.model.Session.Info().Dirty && !a.ctx.WorkingSourceCurrent(a.doc, a.model.PreviewRevision()) {
widgets.LabelWrapped(ui, i18n.Tr("editor.art.preparing_preview", "Preparing the latest authored frame…"), false)
}
content := mathx.Rect{X: cur.X, Y: cur.Y, W: cur.W, H: max(0, area.Y+area.H-cur.Y-ui.Theme.Padding)}
if a.doc.WorkingPreview.Error != "" {
imagePreviewReason(ui, content, a.doc.WorkingPreview.Error)
return
}
surface := imageSurface{}
info := a.model.Session.Document.Info()
surface.Size = mathx.Vec2{X: float32(info.Canvas.Width), Y: float32(info.Canvas.Height)}.Scale(2 / float32(max(info.Canvas.Width, info.Canvas.Height)))
if a.ctx.Loader == nil {
surface.Reason = i18n.Tr("editor.image.preview_no_loader", "The surface preview needs a runtime asset loader and a graphics device.")
} else {
name := artsource.FrameName(frame, art.Albedo)
id := artsource.StableID(a.doc.Asset, name)
if a.ctx.Pipeline != nil {
if prepared, ok := a.ctx.Pipeline.Lookup(a.doc.Path + pipeline.SubSep + name); ok {
id = prepared
}
}
var ok bool
surface.Albedo, ok = a.ctx.Loader.Texture(id)
if !ok {
surface.Reason = i18n.Tr("editor.art.preparing_preview", "Preparing the latest authored frame…")
} else {
_, surface.Normal, _ = a.ctx.Loader.NormalFor(id)
_, surface.Emission, _ = a.ctx.Loader.EmissionFor(id)
}
}
d.surface.draw(a.ctx, content, surface, nil, [4]int{})
}