mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 14:47:34 +00:00
Presenter: Support multiple frames per slide
This commit is contained in:
parent
7c312980b0
commit
fcda397136
8 changed files with 84 additions and 37 deletions
|
@ -11,8 +11,10 @@
|
||||||
"slides": [
|
"slides": [
|
||||||
{
|
{
|
||||||
"title": "Introduction",
|
"title": "Introduction",
|
||||||
|
"frame_count": 2,
|
||||||
"objects": [
|
"objects": [
|
||||||
{
|
{
|
||||||
|
"frame": 0,
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|
||||||
"text": "Welcome to Presenter!",
|
"text": "Welcome to Presenter!",
|
||||||
|
@ -25,6 +27,7 @@
|
||||||
"text-alignment": "Center"
|
"text-alignment": "Center"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"frame": 1,
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|
||||||
"text": "This program is very cool. It supports:\n - Scaling properly to the window\n - Text\n - Switching between slides\n - that's all for now lol",
|
"text": "This program is very cool. It supports:\n - Scaling properly to the window\n - Text\n - Switching between slides\n - that's all for now lol",
|
||||||
|
@ -40,8 +43,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "The Second Slide",
|
"title": "The Second Slide",
|
||||||
|
"frame_count": 2,
|
||||||
"objects": [
|
"objects": [
|
||||||
{
|
{
|
||||||
|
"frame": 0,
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"text": "CatDog likes this program!",
|
"text": "CatDog likes this program!",
|
||||||
|
|
||||||
|
@ -53,6 +58,7 @@
|
||||||
"text-alignment": "Center"
|
"text-alignment": "Center"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"frame": 1,
|
||||||
"type": "image",
|
"type": "image",
|
||||||
|
|
||||||
"rect": [50, 50, 200, 100],
|
"rect": [50, 50, 200, 100],
|
||||||
|
|
|
@ -41,14 +41,20 @@ StringView Presentation::author() const
|
||||||
return "Unknown Author"sv;
|
return "Unknown Author"sv;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Presentation::has_a_next_frame() const
|
bool Presentation::has_next_frame() const
|
||||||
{
|
{
|
||||||
return m_current_slide < u32(m_slides.size() > 1 ? m_slides.size() - 1 : 0);
|
if (m_slides.is_empty())
|
||||||
|
return false;
|
||||||
|
if (m_current_slide.value() < m_slides.size() - 1)
|
||||||
|
return true;
|
||||||
|
return m_current_frame_in_slide < m_slides[m_current_slide.value()].frame_count() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Presentation::has_a_previous_frame() const
|
bool Presentation::has_previous_frame() const
|
||||||
{
|
{
|
||||||
return m_current_slide > 0u;
|
if (m_current_slide > 0u)
|
||||||
|
return true;
|
||||||
|
return m_current_frame_in_slide > 0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Presentation::next_frame()
|
void Presentation::next_frame()
|
||||||
|
@ -65,7 +71,7 @@ void Presentation::previous_frame()
|
||||||
m_current_frame_in_slide.sub(1);
|
m_current_frame_in_slide.sub(1);
|
||||||
if (m_current_frame_in_slide.has_overflow()) {
|
if (m_current_frame_in_slide.has_overflow()) {
|
||||||
m_current_slide.saturating_sub(1);
|
m_current_slide.saturating_sub(1);
|
||||||
m_current_frame_in_slide = m_current_slide == 0u ? 0 : current_slide().frame_count() - 1;
|
m_current_frame_in_slide = current_slide().frame_count() - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,13 +114,15 @@ ErrorOr<NonnullOwnPtr<Presentation>> Presentation::load_from_file(StringView fil
|
||||||
auto presentation = Presentation::construct(size, metadata);
|
auto presentation = Presentation::construct(size, metadata);
|
||||||
|
|
||||||
auto const& slides = maybe_slides.value();
|
auto const& slides = maybe_slides.value();
|
||||||
|
unsigned i = 0;
|
||||||
for (auto const& maybe_slide : slides.values()) {
|
for (auto const& maybe_slide : slides.values()) {
|
||||||
if (!maybe_slide.is_object())
|
if (!maybe_slide.is_object())
|
||||||
return Error::from_string_view("Slides must be objects"sv);
|
return Error::from_string_view("Slides must be objects"sv);
|
||||||
auto const& slide_object = maybe_slide.as_object();
|
auto const& slide_object = maybe_slide.as_object();
|
||||||
|
|
||||||
auto slide = TRY(Slide::parse_slide(slide_object));
|
auto slide = TRY(Slide::parse_slide(slide_object, i));
|
||||||
presentation->append_slide(move(slide));
|
presentation->append_slide(move(slide));
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return presentation;
|
return presentation;
|
||||||
|
@ -163,9 +171,8 @@ ErrorOr<DeprecatedString> Presentation::render()
|
||||||
for (size_t i = 0; i < m_slides.size(); ++i) {
|
for (size_t i = 0; i < m_slides.size(); ++i) {
|
||||||
HTMLElement slide_div;
|
HTMLElement slide_div;
|
||||||
slide_div.tag_name = "div"sv;
|
slide_div.tag_name = "div"sv;
|
||||||
TRY(slide_div.style.try_set("display"sv, "none"sv));
|
|
||||||
TRY(slide_div.attributes.try_set("id"sv, DeprecatedString::formatted("slide{}", i)));
|
TRY(slide_div.attributes.try_set("id"sv, DeprecatedString::formatted("slide{}", i)));
|
||||||
TRY(slide_div.attributes.try_set("class"sv, "slide"));
|
TRY(slide_div.attributes.try_set("class"sv, "slide hidden"sv));
|
||||||
auto& slide = m_slides[i];
|
auto& slide = m_slides[i];
|
||||||
TRY(slide_div.children.try_append(TRY(slide.render(*this))));
|
TRY(slide_div.children.try_append(TRY(slide.render(*this))));
|
||||||
main_element.children.append(move(slide_div));
|
main_element.children.append(move(slide_div));
|
||||||
|
@ -181,18 +188,29 @@ ErrorOr<DeprecatedString> Presentation::render()
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style><script>
|
</style><script>
|
||||||
function goto(slideIndex, frameIndex) {
|
function goto(slideIndex, frameIndex) {
|
||||||
// FIXME: Honor the frameIndex.
|
for (const slide of document.getElementsByClassName("slide")) {
|
||||||
let slide;
|
slide.classList.add("hidden");
|
||||||
for (slide of document.getElementsByClassName("slide")) {
|
}
|
||||||
slide.style.display = "none";
|
for (const frame of document.getElementsByClassName("frame")) {
|
||||||
|
frame.classList.add("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
const slide = document.getElementById(`slide${slideIndex}`);
|
||||||
|
if (slide) slide.classList.remove("hidden");
|
||||||
|
|
||||||
|
for (let i = 0; i <= frameIndex; i++) {
|
||||||
|
for (const frame of document.getElementsByClassName(`slide${slideIndex}-frame${i}`)) {
|
||||||
|
if (frame) frame.classList.remove("hidden");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (slide = document.getElementById(`slide${slideIndex}`))
|
|
||||||
slide.style.display = "block";
|
|
||||||
}
|
}
|
||||||
window.onload = function() { goto(0, 0) }
|
window.onload = function() { goto(0, 0) }
|
||||||
</script><body>
|
</script></head><body>
|
||||||
)"sv));
|
)"sv));
|
||||||
TRY(main_element.serialize(builder));
|
TRY(main_element.serialize(builder));
|
||||||
TRY(builder.try_append("</body></html>"sv));
|
TRY(builder.try_append("</body></html>"sv));
|
||||||
|
|
|
@ -32,8 +32,8 @@ public:
|
||||||
unsigned current_slide_number() const { return m_current_slide.value(); }
|
unsigned current_slide_number() const { return m_current_slide.value(); }
|
||||||
unsigned current_frame_in_slide_number() const { return m_current_frame_in_slide.value(); }
|
unsigned current_frame_in_slide_number() const { return m_current_frame_in_slide.value(); }
|
||||||
|
|
||||||
bool has_a_next_frame() const;
|
bool has_next_frame() const;
|
||||||
bool has_a_previous_frame() const;
|
bool has_previous_frame() const;
|
||||||
void next_frame();
|
void next_frame();
|
||||||
void previous_frame();
|
void previous_frame();
|
||||||
void go_to_first_slide();
|
void go_to_first_slide();
|
||||||
|
|
|
@ -128,8 +128,8 @@ void PresenterWidget::update_web_view()
|
||||||
void PresenterWidget::update_slides_actions()
|
void PresenterWidget::update_slides_actions()
|
||||||
{
|
{
|
||||||
if (m_current_presentation) {
|
if (m_current_presentation) {
|
||||||
m_next_slide_action->set_enabled(m_current_presentation->has_a_next_frame());
|
m_next_slide_action->set_enabled(m_current_presentation->has_next_frame());
|
||||||
m_previous_slide_action->set_enabled(m_current_presentation->has_a_previous_frame());
|
m_previous_slide_action->set_enabled(m_current_presentation->has_previous_frame());
|
||||||
m_full_screen_action->set_enabled(true);
|
m_full_screen_action->set_enabled(true);
|
||||||
m_present_from_first_slide_action->set_enabled(true);
|
m_present_from_first_slide_action->set_enabled(true);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -9,17 +9,18 @@
|
||||||
#include "Presentation.h"
|
#include "Presentation.h"
|
||||||
#include <AK/JsonObject.h>
|
#include <AK/JsonObject.h>
|
||||||
|
|
||||||
Slide::Slide(Vector<NonnullRefPtr<SlideObject>> slide_objects, DeprecatedString title)
|
Slide::Slide(unsigned frame_count, Vector<NonnullRefPtr<SlideObject>> slide_objects, DeprecatedString title)
|
||||||
: m_slide_objects(move(slide_objects))
|
: m_frame_count(move(frame_count))
|
||||||
|
, m_slide_objects(move(slide_objects))
|
||||||
, m_title(move(title))
|
, m_title(move(title))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorOr<Slide> Slide::parse_slide(JsonObject const& slide_json)
|
ErrorOr<Slide> Slide::parse_slide(JsonObject const& slide_json, unsigned slide_index)
|
||||||
{
|
{
|
||||||
// FIXME: Use the text with the "title" role for a title, if there is no title given.
|
// FIXME: Use the text with the "title" role for a title, if there is no title given.
|
||||||
auto title = slide_json.get_deprecated_string("title"sv).value_or("Untitled slide");
|
auto title = slide_json.get_deprecated_string("title"sv).value_or("Untitled slide");
|
||||||
|
auto frame_count = slide_json.get_u32("frame_count"sv).value_or(1);
|
||||||
auto maybe_slide_objects = slide_json.get_array("objects"sv);
|
auto maybe_slide_objects = slide_json.get_array("objects"sv);
|
||||||
if (!maybe_slide_objects.has_value())
|
if (!maybe_slide_objects.has_value())
|
||||||
return Error::from_string_view("Slide objects must be an array"sv);
|
return Error::from_string_view("Slide objects must be an array"sv);
|
||||||
|
@ -31,11 +32,11 @@ ErrorOr<Slide> Slide::parse_slide(JsonObject const& slide_json)
|
||||||
return Error::from_string_view("Slides must be objects"sv);
|
return Error::from_string_view("Slides must be objects"sv);
|
||||||
auto const& slide_object_json = maybe_slide_object_json.as_object();
|
auto const& slide_object_json = maybe_slide_object_json.as_object();
|
||||||
|
|
||||||
auto slide_object = TRY(SlideObject::parse_slide_object(slide_object_json));
|
auto slide_object = TRY(SlideObject::parse_slide_object(slide_object_json, slide_index));
|
||||||
slide_objects.append(move(slide_object));
|
slide_objects.append(move(slide_object));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Slide { move(slide_objects), title };
|
return Slide { frame_count, move(slide_objects), title };
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorOr<HTMLElement> Slide::render(Presentation const& presentation) const
|
ErrorOr<HTMLElement> Slide::render(Presentation const& presentation) const
|
||||||
|
|
|
@ -14,17 +14,17 @@
|
||||||
// A single slide of a presentation.
|
// A single slide of a presentation.
|
||||||
class Slide final {
|
class Slide final {
|
||||||
public:
|
public:
|
||||||
static ErrorOr<Slide> parse_slide(JsonObject const& slide_json);
|
static ErrorOr<Slide> parse_slide(JsonObject const& slide_json, unsigned slide_index);
|
||||||
|
|
||||||
// FIXME: shouldn't be hard-coded to 1.
|
unsigned frame_count() const { return m_frame_count; }
|
||||||
unsigned frame_count() const { return 1; }
|
|
||||||
StringView title() const { return m_title; }
|
StringView title() const { return m_title; }
|
||||||
|
|
||||||
ErrorOr<HTMLElement> render(Presentation const&) const;
|
ErrorOr<HTMLElement> render(Presentation const&) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Slide(Vector<NonnullRefPtr<SlideObject>> slide_objects, DeprecatedString title);
|
Slide(unsigned frame_count, Vector<NonnullRefPtr<SlideObject>> slide_objects, DeprecatedString title);
|
||||||
|
|
||||||
|
unsigned m_frame_count;
|
||||||
Vector<NonnullRefPtr<SlideObject>> m_slide_objects;
|
Vector<NonnullRefPtr<SlideObject>> m_slide_objects;
|
||||||
DeprecatedString m_title;
|
DeprecatedString m_title;
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,8 +17,9 @@ static DeprecatedString to_css_length(float design_value, Presentation const& pr
|
||||||
return DeprecatedString::formatted("{}vw", length_in_vw);
|
return DeprecatedString::formatted("{}vw", length_in_vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorOr<NonnullRefPtr<SlideObject>> SlideObject::parse_slide_object(JsonObject const& slide_object_json)
|
ErrorOr<NonnullRefPtr<SlideObject>> SlideObject::parse_slide_object(JsonObject const& slide_object_json, unsigned slide_index)
|
||||||
{
|
{
|
||||||
|
auto frame = slide_object_json.get_u32("frame"sv).value_or(0);
|
||||||
auto maybe_type = slide_object_json.get_deprecated_string("type"sv);
|
auto maybe_type = slide_object_json.get_deprecated_string("type"sv);
|
||||||
if (!maybe_type.has_value())
|
if (!maybe_type.has_value())
|
||||||
return Error::from_string_view("Slide object must have a type"sv);
|
return Error::from_string_view("Slide object must have a type"sv);
|
||||||
|
@ -26,9 +27,9 @@ ErrorOr<NonnullRefPtr<SlideObject>> SlideObject::parse_slide_object(JsonObject c
|
||||||
auto type = maybe_type.value();
|
auto type = maybe_type.value();
|
||||||
RefPtr<SlideObject> object;
|
RefPtr<SlideObject> object;
|
||||||
if (type == "text"sv)
|
if (type == "text"sv)
|
||||||
object = TRY(try_make_ref_counted<Text>());
|
object = TRY(try_make_ref_counted<Text>(Index { slide_index, frame }));
|
||||||
else if (type == "image"sv)
|
else if (type == "image"sv)
|
||||||
object = TRY(try_make_ref_counted<Image>());
|
object = TRY(try_make_ref_counted<Image>(Index { slide_index, frame }));
|
||||||
else
|
else
|
||||||
return Error::from_string_view("Unsupported slide object type"sv);
|
return Error::from_string_view("Unsupported slide object type"sv);
|
||||||
|
|
||||||
|
@ -97,6 +98,7 @@ ErrorOr<HTMLElement> Text::render(Presentation const& presentation) const
|
||||||
{
|
{
|
||||||
HTMLElement div;
|
HTMLElement div;
|
||||||
div.tag_name = "div"sv;
|
div.tag_name = "div"sv;
|
||||||
|
TRY(div.attributes.try_set("class"sv, DeprecatedString::formatted("frame slide{}-frame{}", m_slide_index, m_frame_index)));
|
||||||
div.style.set("color"sv, m_color.to_deprecated_string());
|
div.style.set("color"sv, m_color.to_deprecated_string());
|
||||||
div.style.set("font-family"sv, DeprecatedString::formatted("'{}'", m_font_family));
|
div.style.set("font-family"sv, DeprecatedString::formatted("'{}'", m_font_family));
|
||||||
div.style.set("font-size"sv, to_css_length(m_font_size_in_pt * 1.33333333f, presentation));
|
div.style.set("font-size"sv, to_css_length(m_font_size_in_pt * 1.33333333f, presentation));
|
||||||
|
@ -125,6 +127,7 @@ ErrorOr<HTMLElement> Image::render(Presentation const& presentation) const
|
||||||
|
|
||||||
HTMLElement image_wrapper;
|
HTMLElement image_wrapper;
|
||||||
image_wrapper.tag_name = "div"sv;
|
image_wrapper.tag_name = "div"sv;
|
||||||
|
TRY(image_wrapper.attributes.try_set("class"sv, DeprecatedString::formatted("frame slide{}-frame{}", m_slide_index, m_frame_index)));
|
||||||
image_wrapper.children.append(move(img));
|
image_wrapper.children.append(move(img));
|
||||||
image_wrapper.style.set("position"sv, "absolute"sv);
|
image_wrapper.style.set("position"sv, "absolute"sv);
|
||||||
image_wrapper.style.set("left"sv, to_css_length(m_rect.left(), presentation));
|
image_wrapper.style.set("left"sv, to_css_length(m_rect.left(), presentation));
|
||||||
|
|
|
@ -21,19 +21,29 @@ struct HTMLElement {
|
||||||
|
|
||||||
ErrorOr<void> serialize(StringBuilder&) const;
|
ErrorOr<void> serialize(StringBuilder&) const;
|
||||||
};
|
};
|
||||||
|
struct Index {
|
||||||
|
unsigned slide;
|
||||||
|
unsigned frame;
|
||||||
|
};
|
||||||
|
|
||||||
// Anything that can be on a slide.
|
// Anything that can be on a slide.
|
||||||
class SlideObject : public RefCounted<SlideObject> {
|
class SlideObject : public RefCounted<SlideObject> {
|
||||||
public:
|
public:
|
||||||
virtual ~SlideObject() = default;
|
virtual ~SlideObject() = default;
|
||||||
static ErrorOr<NonnullRefPtr<SlideObject>> parse_slide_object(JsonObject const& slide_object_json);
|
static ErrorOr<NonnullRefPtr<SlideObject>> parse_slide_object(JsonObject const& slide_object_json, unsigned slide_index);
|
||||||
virtual ErrorOr<HTMLElement> render(Presentation const&) const = 0;
|
virtual ErrorOr<HTMLElement> render(Presentation const&) const = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SlideObject() = default;
|
SlideObject(Index index)
|
||||||
|
: m_frame_index(index.frame)
|
||||||
|
, m_slide_index(index.slide)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
virtual void set_property(StringView name, JsonValue);
|
virtual void set_property(StringView name, JsonValue);
|
||||||
|
|
||||||
|
unsigned m_frame_index;
|
||||||
|
unsigned m_slide_index;
|
||||||
HashMap<DeprecatedString, JsonValue> m_properties;
|
HashMap<DeprecatedString, JsonValue> m_properties;
|
||||||
Gfx::IntRect m_rect;
|
Gfx::IntRect m_rect;
|
||||||
};
|
};
|
||||||
|
@ -44,7 +54,10 @@ public:
|
||||||
virtual ~GraphicsObject() = default;
|
virtual ~GraphicsObject() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
GraphicsObject() = default;
|
GraphicsObject(Index index)
|
||||||
|
: SlideObject(index)
|
||||||
|
{
|
||||||
|
}
|
||||||
virtual void set_property(StringView name, JsonValue) override;
|
virtual void set_property(StringView name, JsonValue) override;
|
||||||
|
|
||||||
// FIXME: Change the default color based on the color scheme
|
// FIXME: Change the default color based on the color scheme
|
||||||
|
@ -53,7 +66,10 @@ protected:
|
||||||
|
|
||||||
class Text final : public GraphicsObject {
|
class Text final : public GraphicsObject {
|
||||||
public:
|
public:
|
||||||
Text() = default;
|
Text(Index index)
|
||||||
|
: GraphicsObject(index)
|
||||||
|
{
|
||||||
|
}
|
||||||
virtual ~Text() = default;
|
virtual ~Text() = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -69,7 +85,10 @@ private:
|
||||||
|
|
||||||
class Image final : public SlideObject {
|
class Image final : public SlideObject {
|
||||||
public:
|
public:
|
||||||
Image() = default;
|
Image(Index index)
|
||||||
|
: SlideObject(index)
|
||||||
|
{
|
||||||
|
}
|
||||||
virtual ~Image() = default;
|
virtual ~Image() = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue